1. 程式人生 > >【劍指Offer學習】【面試題37:兩個連結串列的第一個公共結點】

【劍指Offer學習】【面試題37:兩個連結串列的第一個公共結點】

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

連結串列結點定義

    /**
     * 連結串列結點類
     */
    private static class ListNode {
        int val;
        ListNode next;

        public ListNode() {

        }

        public ListNode(int val) {
            this.val = val;
        }

        @Override
        public String toString
() { return val + ""; } }

解題思路:

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

第二種:使用棧
  所以兩個有公共結點而部分重舍的鏈衰,拓撲形狀看起來像一個Y, 而不可能像X(如圖5.3 所示)。

這裡寫圖片描述

  經過分析我們發現,如果兩個連結串列有公共結點,那麼公共結點出現在兩個連結串列的尾部。如果我們從兩個鏈衰的尾部開始往前比較,最後一個相同的結點就是我們要找的結點。
  在上述思路中,我們需要用兩個輔助錢。如果連結串列的長度分別為m 和n,那麼空間複雜度是O(m+n)。這種思路的時間複雜度也是O(m+n)。和最開始的蠻力法相比,時間效率得到了提高,相當於是用空間消耗換取了時間效率。

第三種:先行法
  在圖5.3 的兩個連結串列中,我們可以先遍歷一次得到它們的長度分別為5 和4, 也就是較長的連結串列與較短的連結串列相比多一個結點。第二次先在長的連結串列上走1 步,到達結點2。接下來分別從結點2 和結點4 出發同時遍歷兩個結點, 直到找到它們第一個相同的結點6,這就是我們想要的結果。
  第三種思路和第二種思路相比,時間複雜度都是O(m+啡, 但我們不再需要輔助的攏,因此提高了空間效率。

本題採用第三種解法

程式碼實現

public class Test37 {
    /**
     * 連結串列結點類
     */
    private static class
ListNode {
int val; ListNode next; public ListNode() { } public ListNode(int val) { this.val = val; } @Override public String toString() { return val + ""; } } /** * 找兩個結點的第一個公共結點,如果沒有找到返回null,方法比較好,考慮了兩個連結串列中有null的情況 * * @param head1 第一個連結串列 * @param head2 第二個連結串列 * @return 找到的公共結點,沒有返回null */ public static ListNode findFirstCommonNode(ListNode head1, ListNode head2) { int length1 = getListLength(head1); int length2 = getListLength(head2); int diff = length1 - length2; ListNode longListHead = head1; ListNode shortListHead = head2; if (diff < 0) { longListHead = head2; shortListHead = head1; diff = length2 - length1; } for (int i = 0; i < diff; i++) { longListHead = longListHead.next; } while (longListHead != null && shortListHead != null && longListHead != shortListHead) { longListHead = longListHead.next; shortListHead = shortListHead.next; } // 返回第一個相同的公共結點,如果沒有返回null return longListHead; } private static int getListLength(ListNode head) { int result = 0; while (head != null) { result++; head = head.next; } return result; } public static void main(String[] args) { test1(); test2(); test3(); test4(); } private static void test1() { // 第一個公共結點在連結串列中間 // 1 - 2 - 3 \ // 6 - 7 // 4 - 5 / ListNode n1 = new ListNode(1); ListNode n2 = new ListNode(2); ListNode n3 = new ListNode(3); ListNode n4 = new ListNode(4); ListNode n5 = new ListNode(5); ListNode n6 = new ListNode(6); ListNode n7 = new ListNode(7); n1.next = n2; n2.next = n3; n3.next = n6; n6.next = n7; n4.next = n5; n5.next = n6; System.out.println(findFirstCommonNode(n1, n4)); // 6 } private static void test2() { // 沒有公共結點 // 1 - 2 - 3 - 4 // // 5 - 6 - 7 ListNode n1 = new ListNode(1); ListNode n2 = new ListNode(2); ListNode n3 = new ListNode(3); ListNode n4 = new ListNode(4); ListNode n5 = new ListNode(5); ListNode n6 = new ListNode(6); ListNode n7 = new ListNode(7); n1.next = n2; n2.next = n3; n3.next = n4; n5.next = n6; n6.next = n7; System.out.println(findFirstCommonNode(n1, n5)); // null } private static void test3() { // 公共結點是最後一個結點 // 1 - 2 - 3 - 4 \ // 7 // 5 - 6 / ListNode n1 = new ListNode(1); ListNode n2 = new ListNode(2); ListNode n3 = new ListNode(3); ListNode n4 = new ListNode(4); ListNode n5 = new ListNode(5); ListNode n6 = new ListNode(6); ListNode n7 = new ListNode(7); n1.next = n2; n2.next = n3; n3.next = n4; n4.next = n7; n5.next = n6; n6.next = n7; System.out.println(findFirstCommonNode(n1, n5)); // 7 } private static void test4() { // 公共結點是第一個結點 // 1 - 2 - 3 - 4 - 5 // 兩個連結串列完全重合 ListNode n1 = new ListNode(1); ListNode n2 = new ListNode(2); ListNode n3 = new ListNode(3); ListNode n4 = new ListNode(4); ListNode n5 = new ListNode(5); ListNode n6 = new ListNode(6); ListNode n7 = new ListNode(7); n1.next = n2; n2.next = n3; n3.next = n4; n4.next = n5; System.out.println(findFirstCommonNode(n1, n1)); // 1 } }

執行結果

這裡寫圖片描述