力扣 - 劍指 Offer 52. 兩個連結串列的第一個公共節點

linzeliang發表於2021-11-20

題目

劍指 Offer 52. 兩個連結串列的第一個公共節點

思路1(棧)

  • 若兩個連結串列相遇,則從它開始相遇的地方到連結串列末尾應該都是相同的,那麼我們可以將兩個連結串列分別放入兩個棧中,然後依次迴圈比較兩個棧頂的節點,同時用pre記錄上一個節點,直到當前兩個棧頂節點不相等,那麼pre節點就是他們開始相遇的地方

程式碼

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        LinkedList<ListNode> stack1 = new LinkedList<>();
        LinkedList<ListNode> stack2 = new LinkedList<>();
        
        // 使用兩個棧分別儲存兩個連結串列的元素
        while (headA != null) {
            stack1.push(headA);
            headA = headA.next;
        }
        while (headB != null) {
            stack2.push(headB);
            headB = headB.next;
        }

        // 若兩個連結串列存在相同的節點,則兩個棧pop出來的節點應該是一樣的
        // 如果pop出來的不一樣,說明上一個pop出來相同節點才是相遇開始的節點
        // 因此我們需要pre來記錄上一個相同的節點,初始值要為null,如果沒有相遇,那麼會直接返回null
        ListNode pre = null;
        while (!stack1.isEmpty() && !stack2.isEmpty()) {
            ListNode p1 = stack1.pop();
            ListNode p2 = stack2.pop();
            if (p1 != p2) {
                break;
            }
            // 如果沒有相遇,就不會執行這條語句,因此此時pre為 null,返回的值也是 null
            pre = p1;
        }

        // 返回結果
        return pre;
    }
}

複雜度分析

  • 時間複雜度:\(O(N+M)\)
  • 空間複雜度:\(O(N+M)\)

思路2(差值法)

  • 先分別計算兩個連結串列的長度記為lengthAlengthB,得到他們的差值n,讓長的那個連結串列先走n步,然後兩個連結串列再一起走,第一個相等的地方,則是連結串列開始相遇的地方

程式碼

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        int lengthA = 0;
        int lengthB = 0;

        ListNode pA = headA;
        ListNode pB = headB;

        // 先獲取兩個連結串列的長度
        while (pA != null) {
            pA = pA.next;
            lengthA++;
        }
        while (pB != null) {
            pB = pB.next;
            lengthB++;
        }

        // 計算連結串列長度差n,將長的連結串列先走n步
        int n = lengthA > lengthB ? lengthA - lengthB : lengthB - lengthA;
        while (lengthA > lengthB && n > 0) {
            headA = headA.next;
            n--;
        }
        while (lengthB > lengthA && n > 0) {
            headB = headB.next;
            n--;
        }

        // 然後兩個連結串列才開始同時一起走
        // 知道兩個節點相等 或者 都為null的時候結束迴圈
        while (headA != headB) {
            headA = headA.next;
            headB = headB.next;
        }
        
        return headA;
    }
}

複雜度分析

  • 時間複雜度:\(O(N+M)\)
  • 空間複雜度:\(O(1)\)

思路3(雙指標)

  • 使用兩個指標pApB分別指向兩個連結串列,然後同時前進,如果到了連結串列末尾,就跳到另一條連結串列的頭節點(要保證不會再跳回來,否則導致死迴圈)
  • 在這過程中,要判斷節點是否相等,第一次相等的地方就是相交的地方,如果沒有那就返回null
  • while中的判斷條件要為當前兩個節點是否相等,因為不這樣的話,如果不存在相交的節點,那麼迴圈就會一直重複下去;如果判斷條件為當前兩個節點是否相等的話,如果沒有相交節點也不會一直迴圈下去,因為兩個指標遍歷的長度都為兩個連結串列長度之和,所以最後都會指向null,此時都為null相等,跳出迴圈

程式碼

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        
        ListNode pA = headA;
        ListNode pB = headB;

        // 判斷是否相等,遇到相同節點,結束迴圈;如果連結串列中沒有相同節點,那麼跳出迴圈的條件是他們都為 null 的時候
        while (pA != pB) {
            // 判斷的要為當前節點
            // 如果判斷的是next節點,那麼到時候就會導致pA、pB永遠不會是null
            if (pA == null) {
                pA = headB;
            } else {
                pA = pA.next;
            }
            if (pB == null) {
                pB = headA;
            } else {
                pB = pB.next;
            }
        }
        return pA;
    }
}

複雜度分析

  • 時間複雜度:\(O(N)\)
  • 空間複雜度:\(O(1)\)

思路4(雜湊表)

  • 先選擇其中一個連結串列,將其節點全部加入到雜湊表中,然後再開始遍歷另一個連結串列,同時也加入到雜湊表中
  • 如果加入的節點和雜湊表衝突了,那麼這個節點就是交點

程式碼

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        HashSet<ListNode> set = new HashSet<>();
        // 遍歷一次,將連結串列A裡的所有節點新增到set中
        while (headA != null) {
            set.add(headA);
            headA = headA.next;
        }

        // 再遍歷一次連結串列B,同時將連結串列的節點新增到set中,如果新增失敗返回false,那麼說明找到相同的那個節點了,直接返回該節點
        while (headB != null) {
            if (!set.add(headB)) {
                return headB;
            }
            headB = headB.next;
        }

        // 如果遍歷連結串列B的時候都沒有找到,說明不存在相同的節點,就返回 null
        return null;
    }
}

複雜度分析

  • 時間複雜度:\(O(N+M)\)
  • 空間複雜度:\(O(N)\),雜湊表所佔空間大小

相關文章