1. 程式人生 > >劍指Offer面試題:31.兩個連結串列的第一個公共節點

劍指Offer面試題:31.兩個連結串列的第一個公共節點

一、題目:兩個連結串列的第一個公共節點

題目:輸入兩個連結串列,找出它們的第一個公共結點。

  連結串列結點定義如下,這裡使用C#語言描述:

    public class Node
    {
        public int key;
        public Node nextNode;

        public Node(int key)
        {
            this.key = key;
        }
    }

二、解題思路

2.1 蠻力法

  碰到這道題,很多人的第一反應就是蠻力法:在第一連結串列上順序遍歷每個結點,每遍歷到一個結點的時候,在第二個連結串列上順序遍歷每個結點。如果在第二個連結串列上有一個結點和第一個連結串列上的結點一樣,說明兩個連結串列在這個結點上重合,於是就找到了它們的公共結點。如果第一個連結串列的長度為m,第二個連結串列的長度為n,顯然該方法的時間複雜度是O(mn)

2.2 藉助外部空間法

  首先,經過分析我們發現兩個有公共結點而部分重合的連結串列,拓撲形狀看起來像一個Y,而不可能像X,如下圖所示,兩個連結串列在值為6的結點處交匯:

  如果兩個連結串列有公共結點,那麼公共結點出現在兩個連結串列的尾部。如果我們從兩個連結串列的尾部開始往前比較,最後一個相同的結點就是我們要找的結點。But,在單鏈表中只能從頭結點開始按順序遍歷,最後才能到達尾結點。最後到達的尾結點卻要最先被比較,這是“後進先出”的特性。於是,我們可以使用棧的特點來解決這個問題:分別把兩個連結串列的結點放入兩個棧裡,這樣兩個連結串列的尾結點就位於兩個棧的棧頂,接下來比較兩個棧頂的結點是否相同。如果相同,則把棧頂彈出接著比較下一個棧頂,直到找到最後一個相同的結點

    public static Node FindFirstCommonNode(Node head1, Node head2)
    {
        if(head1 == null || head2 == null)
        {
            return null;
        }

        Stack<Node> stack1 = new Stack<Node>();
        Stack<Node> stack2 = new Stack<Node>();

        
while(head1 != null) { stack1.Push(head1); head1 = head1.nextNode; } while(head2 != null) { stack2.Push(head2); head2 = head2.nextNode; } Node node1 = null; Node node2 = null; Node common = null; while(stack1.Count > 0 && stack2.Count > 0) { node1 = stack1.Peek(); node2 = stack2.Peek(); if (node1.key == node2.key) { common = node1; stack1.Pop(); stack2.Pop(); } else { break; } } return common; }

  在上述思路中,我們需要用兩個輔助棧。如果連結串列的長度分別為m和n,那麼空間複雜度是O(m+n)。這種思路的時間複雜度也是O(m+n)。和最開始的蠻力法相比,時間效率得到了提高,相當於是用空間消耗換取了時間效率

2.3 不借助外部空間法

  那麼,可不可以不借助棧來實現了呢?答案是可以的,我們可以首先遍歷兩個連結串列得到它們的長度,就能知道哪個連結串列比較長,以及長的連結串列比短的連結串列多幾個結點。在第二次遍歷的時候,在較長的連結串列上先走若干步,接著再同時在兩個連結串列上遍歷,找到的第一個相同的結點就是它們的第一個公共結點

  比如在上圖的兩個連結串列中,我們可以先遍歷一次得到它們的長度分別為5和4,也就是較長的連結串列與較短的連結串列相比多一個結點。第二次先在長的連結串列上走1步,到達結點2。接下來分別從結點2和結點4出發同時遍歷兩個結點,直到找到它們第一個相同的結點6,這就是我們想要的結果。

    public static Node FindFirstCommonNode(Node head1, Node head2)
    {
        // 得到兩個連結串列的長度
        int length1 = GetListLength(head1);
        int length2 = GetListLength(head2);
        int diff = length1 - length2;

        Node headLong = head1;
        Node headShort = head2;
        if (diff < 0)
        {
            headLong = head2;
            headShort = head1;
            diff = length2 - length1;
        }
        // 先在長連結串列上走幾步
        for (int i = 0; i < diff; i++)
        {
            headLong = headLong.nextNode;
        }
        // 再同時在兩個連結串列上遍歷
        while (headLong != null && headShort != null && headLong != headShort)
        {
            headLong = headLong.nextNode;
            headShort = headShort.nextNode;
        }

        Node commonNode = headLong;
        return commonNode;
    }

    private static int GetListLength(Node head)
    {
        int length = 0;
        Node tempNode = head;
        while (tempNode != null)
        {
            tempNode = tempNode.nextNode;
            length++;
        }

        return length;
    }

  上述思路與藉助棧的方法的時間複雜度都是O(m+n),但我們不再需要輔助的棧,因此提高了空間效率。

三、單元測試

3.1 測試用例:功能測試與特殊輸入測試

    [TestClass]
    public class CommonNodeHelperTest
    {
        private void DestoryNode(Node node)
        {
            if (node != null)
            {
                node = null;
            }
        }

        // 第一個公共結點在連結串列中間
        // 1 - 2 - 3 \
        //            6 - 7
        //     4 - 5 /
        [TestMethod]
        public void FindTest1()
        {
            Node node1 = new Node(1);
            Node node2 = new Node(2);
            Node node3 = new Node(3);
            Node node4 = new Node(4);
            Node node5 = new Node(5);
            Node node6 = new Node(6);
            Node node7 = new Node(7);

            // first
            node1.nextNode = node2;
            node2.nextNode = node3;
            node3.nextNode = node6;
            node6.nextNode = node7;
            // second
            node4.nextNode = node5;
            node5.nextNode = node6;

            Node actual = CommonNodeHelper.FindFirstCommonNode(node1, node4);
            Assert.AreEqual(actual.key, 6);

            DestoryNode(node1);
            DestoryNode(node2);
            DestoryNode(node3);
            DestoryNode(node4);
            DestoryNode(node5);
            DestoryNode(node6);
            DestoryNode(node7);
        }

        // 沒有公共結點
        // 1 - 2 - 3 - 4
        //            
        // 5 - 6 - 7
        [TestMethod]
        public void FindTest2()
        {
            Node node1 = new Node(1);
            Node node2 = new Node(2);
            Node node3 = new Node(3);
            Node node4 = new Node(4);
            Node node5 = new Node(5);
            Node node6 = new Node(6);
            Node node7 = new Node(7);

            // first
            node1.nextNode = node2;
            node2.nextNode = node3;
            node3.nextNode = node4;
            
            // second
            node5.nextNode = node6;
            node6.nextNode = node7;

            Node actual = CommonNodeHelper.FindFirstCommonNode(node1, node5);
            Assert.AreEqual(actual, null);

            DestoryNode(node1);
            DestoryNode(node2);
            DestoryNode(node3);
            DestoryNode(node4);
            DestoryNode(node5);
            DestoryNode(node6);
            DestoryNode(node7);
        }

        // 公共結點是最後一個結點
        //         5 - 6 \
        //                7
        // 1 - 2 - 3 - 4 /
        [TestMethod]
        public void FindTest3()
        {
            Node node1 = new Node(1);
            Node node2 = new Node(2);
            Node node3 = new Node(3);
            Node node4 = new Node(4);
            Node node5 = new Node(5);
            Node node6 = new Node(6);
            Node node7 = new Node(7);

            // first
            node1.nextNode = node2;
            node2.nextNode = node3;
            node3.nextNode = node4;
            node4.nextNode = node7;
            // second
            node5.nextNode = node6;
            node6.nextNode = node7;

            Node actual = CommonNodeHelper.FindFirstCommonNode(node5, node1);
            Assert.AreEqual(actual.key, 7);

            DestoryNode(node1);
            DestoryNode(node2);
            DestoryNode(node3);
            DestoryNode(node4);
            DestoryNode(node5);
            DestoryNode(node6);
            DestoryNode(node7);
        }

        // 公共結點是第一個結點
        // 1 - 2 - 3 - 4 - 5
        // 兩個連結串列完全重合   
        [TestMethod]
        public void FindTest4()
        {
            Node node1 = new Node(1);
            Node node2 = new Node(2);
            Node node3 = new Node(3);
            Node node4 = new Node(4);
            Node node5 = new Node(5);
            Node node6 = new Node(6);
            Node node7 = new Node(7);

            // first & second
            node1.nextNode = node2;
            node2.nextNode = node3;
            node3.nextNode = node4;
            node4.nextNode = node5;

            Node actual = CommonNodeHelper.FindFirstCommonNode(node1, node1);
            Assert.AreEqual(actual.key, 1);

            DestoryNode(node1);
            DestoryNode(node2);
            DestoryNode(node3);
            DestoryNode(node4);
            DestoryNode(node5);
            DestoryNode(node6);
            DestoryNode(node7);
        }

        // 輸入的兩個連結串列有一個空連結串列
        [TestMethod]
        public void FindTest5()
        {
            Node node1 = new Node(1);
            Node node2 = new Node(2);
            Node node3 = new Node(3);
            Node node4 = new Node(4);
            Node node5 = new Node(5);

            // first & second
            node1.nextNode = node2;
            node2.nextNode = node3;
            node3.nextNode = node4;
            node4.nextNode = node5;

            Node actual = CommonNodeHelper.FindFirstCommonNode(node1, null);
            Assert.AreEqual(actual, null);

            DestoryNode(node1);
            DestoryNode(node2);
            DestoryNode(node3);
            DestoryNode(node4);
            DestoryNode(node5);
        }

        // 輸入的兩個連結串列均為空連結串列
        [TestMethod]
        public void FindTest6()
        {
            Node actual = CommonNodeHelper.FindFirstCommonNode(null, null);
            Assert.AreEqual(actual, null);
        }
    }

3.2 測試結果:用例通過情況與程式碼覆蓋率

  (1)用例通過情況

  (2)程式碼覆蓋率

作者:周旭龍

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連結。

相關推薦

Offer試題31.連結串列第一公共節點

一、題目:兩個連結串列的第一個公共節點 題目:輸入兩個連結串列,找出它們的第一個公共結點。   連結串列結點定義如下,這裡使用C#語言描述: public class Node { public int key; public Node

Offer:試題26——複製複雜的連結串列(java實現)

問題描述: 輸入一個複雜連結串列(每個節點中有節點值,以及兩個指標,一個指向下一個節點,另一個特殊指標指向任意一個節點)。 思路1: 1.先複製連結串列節點,並用next連結起來。

Offer試題6.用棧實現佇列

一、題目:用兩個棧實現佇列 題目:用兩個棧實現一個佇列。佇列的宣告如下,請實現它的兩個函式appendTail和deleteHead,分別完成在佇列尾部插入結點和在佇列頭部刪除結點的功能。   原文是使用C++結合模板實現的定義,這裡我們採用C#結合泛型來實現這個佇列的定義,我們要實現的就是兩

Offer試題16.合併排序的連結串列

PS:這也是一道出鏡率極高的面試題,我相信很多童鞋都會很眼熟,就像於千萬人之中遇見不期而遇的人,沒有別的話可說,唯有輕輕地問一聲:“哦,原來你也在這裡? ” 一、題目:合併兩個排序的連結串列 題目:輸入兩個遞增排序的連結串列,合併這兩個連結串列並使新連結串列中的結點仍然是按照遞增排序的。例如輸入下

牛客網線上程式設計專題《offer-試題37》連結串列第一公共結點

題目連結: 題目描述: 解題思路: 首先遍歷兩個連結串列得到它們的長度,就能知道哪個連結串列比較長,以及長的連結串列比短的連結串列多幾個結點。在第二次遍歷的時候,在較長的連結串列上先走若干步,接著再同時在兩個連結串列上遍歷,找到的第一個相同的結點就是它們的第

offer 試題重建二叉樹

題目:輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建二叉樹並返回。 思路:二叉樹先序是根左右,中序 是左根右。所以先找到

offer 試題從尾到頭列印連結串列

題目:輸入一個連結串列,按連結串列值從尾到頭的順序返回一個ArrayList。 思路:有多種放法。(1)先反轉連結串列,再列印連結串列。(2)使用棧。 /** * struct ListNode { * int val; * struct ListNode *n

Offer:試題7——用棧實現佇列(java實現)

題目描述:用兩個棧來實現一個佇列,完成佇列的Push和Pop操作。 佇列中的元素為int型別。 首先定義兩個棧 Stack<Integer> stack1 = new Stack<Integer>();//作為進隊的埠 Stack

Offer試題17.樹的子結構

一、題目:樹的子結構 題目:輸入兩棵二叉樹A和B,判斷B是不是A的子結構。例如下圖中的兩棵二叉樹,由於A中有一部分子樹的結構和B是一樣的,因此B是A的子結構。   該二叉樹的節點定義如下,這裡使用C#語言描述: public class BinaryTreeNode {

Offer試題2.二維陣列中的查詢

一、題目:二維陣列中的查詢 題目:在一個二維陣列中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函式,輸入這樣的一個二維陣列和一個整數,判斷陣列中是否含有該整數。     例如下面的二維陣列就是每行、每列都遞增排序。如果在這個陣列中查詢數字7,則返回true;

Offer試題22.二叉搜尋樹的後序遍歷序列

一、題目:二叉搜尋樹的後序遍歷序列 題目:輸入一個整數陣列,判斷該陣列是不是某二叉搜尋樹的後序遍歷的結果。如果是則返回true,否則返回false。假設輸入的陣列的任意兩個數字都互不相同。   例如在下面的一顆二叉搜尋樹中,輸入陣列{5,7,6,9,11,10,8},則返回true,因為這個整數序列是

Offer試題14.連結串列的倒數第k節點

PS:這是一道出境率極高的題目,記得去年參加校園招聘時我看到了3次,但是每次寫的都不完善。 一、題目:連結串列的倒數第k個節點 題目:輸入一個連結串列,輸出該連結串列中倒數第k個結點。為了符合大多數人的習慣,本題從1開始計數,即連結串列的尾結點是倒數第1個結點。例如一個連結串列有6個結點,從頭結點開始

Offer試題28.連續子陣列的最大和

一、題目:連續子陣列的最大和 題目:輸入一個整型陣列,數組裡有正數也有負數。陣列中一個或連續的多個整陣列成一個子陣列。求所有子陣列的和的最大值。要求時間複雜度為O(n)。例如輸入的陣列為{1,-2,3,10,-4,7,2,-5},和最大的子陣列為{3,10,-4,7,2},因此輸出為該子陣列的和18。

Offer試題4.從尾到頭列印連結串列

一、題目:從尾到頭列印連結串列 題目:輸入一個連結串列的頭結點,從尾到頭反過來打印出每個結點的值。   到解決這個問題肯定要遍歷連結串列。遍歷的順序是從頭到尾的順序,可輸出的順序卻是從尾到頭。也就是說第一個遍歷到的結點最後一個輸出,而最後一個遍歷到的結點第一個輸出。這就是典型的“後進先出”,我

Offer試題10.數值的整數次方

一、題目:數值的整數次方 題目:實現函式double Power(doublebase, int exponent),求base的exponent次方。不得使用庫函式,同時不需要考慮大數問題。   在.NET Framework提供的BCL中,Math類實現了一個Pow方法,例如要求2的三次方,可

Offer試題20.棧的壓入、彈出序列

一、題目:棧的壓入、彈出序列 題目:輸入兩個整數序列,第一個序列表示棧的壓入順序,請判斷第二個序列是否為該棧的彈出順序。假設壓入棧的所有數字均不相等。例如序列1、2、3、4、5是某棧的壓棧序列,序列4、5、3、2、1是該壓棧序列對應的一個彈出序列,但4、3、5、1、2就不可能是該壓棧序列的彈出序列。

Offer試題30.第一只出現一次的字元

一、題目:第一個只出現一次的字元 題目:在字串中找出第一個只出現一次的字元。如輸入"abaccdeff",則輸出'b'。要求時間複雜度為O(n)。   最直觀的想法是從頭開始掃描這個字串中的每個字元。當訪問到某字元時拿這個字元和後面的每個字元相比較,如果在後面沒有發現重複的字元,則該字元就是隻出現

Offer試題34.翻轉單詞順序VS左旋轉字串

一、題目一:翻轉單詞順序 1.1 題目說明 題目一:輸入一個英文句子,翻轉句子中單詞的順序,但單詞內字元的順序不變。為簡單起見,標點符號和普通字母一樣處理。例如輸入字串"I am a student.",則輸出"student. a am I"。 1.2 解題思路   第一步翻轉句子中所有的字

Offer試題23.二叉樹中和為某一值的路徑

一、題目:二叉樹中和為某一值的路徑 題目:輸入一棵二叉樹和一個整數,打印出二叉樹中結點值的和為輸入整數的所有路徑。從樹的根結點開始往下一直到葉結點所經過的結點形成一條路徑。例如輸入下圖中二叉樹和整數22,則打印出兩條路徑,第一條路徑包含結點10、12,第二條路徑包含結點10、5和7。   二叉

Offer試題11.列印1到最大的n位數

一、題目:列印1到最大的n位數 題目:輸入數字n,按順序打印出從1最大的n位十進位制數。比如輸入3,則打印出1、2、3一直到最大的3位數即999。 二、不同的解法 2.1 不假思索的解法   最容易想到的辦法是先求出最大的n位數,然後用一個迴圈從1開始逐個列印: static v