劍指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)代碼覆蓋率

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM