LeetCode#160-Intersection of Two Linked Lists-相交鏈表

一、題目

編寫一個程序,找到兩個單鏈表相交的起始節點。
如下面的兩個鏈表:

在節點 c1 開始相交。

示例 1:

輸入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
輸出:Reference of the node with value = 8
輸入解釋:相交節點的值爲 8 (注意,如果兩個列表相交則不能爲 0)。
從各自的表頭開始算起,鏈表 A 爲 [4,1,8,4,5],鏈表 B 爲 [5,0,1,8,4,5]。
在 A 中,相交節點前有 2 個節點;在 B 中,相交節點前有 3 個節點。

示例 2:

輸入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
輸出:Reference of the node with value = 2
輸入解釋:相交節點的值爲 2 (注意,如果兩個列表相交則不能爲 0)。
從各自的表頭開始算起,鏈表 A 爲 [0,9,1,2,4],鏈表 B 爲 [3,2,4]。
在 A 中,相交節點前有 3 個節點;在 B 中,相交節點前有 1 個節點。

示例 3:

輸入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
輸出:null
輸入解釋:從各自的表頭開始算起,鏈表 A 爲 [2,6,4],鏈表 B 爲 [1,5]。由
於這兩個鏈表不相交,所以 intersectVal 必須爲 0,而 skipA 和 skipB 可以是任意值。
解釋:這兩個鏈表不相交,因此返回 null。

注意:

  • 如果兩個鏈表沒有交點,返回 null.
  • 在返回結果後,兩個鏈表仍須保持原有的結構。
  • 可假定整個鏈表結構中沒有循環。
  • 程序儘量滿足 O(n) 時間複雜度,且僅用 O(1) 內存。

二、題解

  • 題解1:哈希表

遍歷鏈表 A 並將每個節點的地址/引用存儲在哈希表中。
然後檢查鏈表 B 中的每一個節點是否在哈希表中。若在,則爲相交結點。

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
 func getIntersectionNode(headA, headB *ListNode) *ListNode {
    var s [] *ListNode
    
    for headA != nil {
        s = append(s, headA)
        headA = headA.Next
    }
    for headB != nil {
        for _,v := range(s) {
            if v == headB {
                return headB
            }
        }
        headB = headB.Next
    }
    return nil
}
  • 題解2:鏈表拼接法

當鏈表 A 走到尾部的 null 時,轉到鏈表 B 的頭節點繼續走;
當鏈表 B 走到尾部的 null 時,轉到鏈表 A 的頭節點繼續走;
若兩鏈表相交,則 A 和 B 一定相遇。

如上圖:初始化 pA = headA, pB = headB,開始遍歷。
pA會先到達鏈表尾,當pA到達末尾時,重置pA爲headB;同樣的,當pB到達末尾時,重置pB爲headA。當pA與pB相遇時,必然就是兩個鏈表的交點。

爲什麼要這樣處理?因爲對 pA 而言,走過的路程爲 a+c+b,對 pB 而言,爲 b+c+a,顯然 a+c+b = b+c+a,這就是該算法的核心原理。

即使兩個鏈表沒有相交點,仍然可以統一處理,因爲這種情況意味着相交點就是 null,也就是上圖中的公共部分c沒有了,從而遞推式變成了 pA: a+bpB: b+a,同樣是成立的。

時間複雜度:O(m+n),空間複雜度:O(1)。

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
 func getIntersectionNode(headA, headB *ListNode) *ListNode {
    if (headA == nil || headB == nil) {
        return nil
    }
    pA, pB := headA, headB
    for pA != pB {
        if pA == nil {
            pA = headB 
        } else {
            pA = pA.Next
        }
        if pB == nil {
            pB = headA
        } else {
            pB = pB.Next
        }
    }
    return pA
}
  • 題解3:消除鏈表長度差

兩個單鏈表,有公共結點,則必然尾部公用;

分別找出鏈表 1 和鏈表 2 的長度,長的鏈表減去短的鏈表得出一個 n 值;

長的鏈表先走 n 步,兩個鏈表再同時移動,則兩個鏈表相交點就是第一個公共結點。

時間複雜度:O(n),空間複雜度:O(1)。

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func getIntersectionNode(headA, headB *ListNode) *ListNode {
    if headA == nil || headB == nil {
        return nil
    }
    lenA := getLen(headA)
    lenB := getLen(headB)

    //計算鏈表長度差 n,長的先移動 n 步
    if (lenA > lenB) {// 鏈表A比鏈表B長,A先移動
        for i := 0; i < lenA - lenB; i++ {
            headA = headA.Next
        }
    } else {// 鏈表B比鏈表A長,B先移動
        for i := 0; i < lenB - lenA; i++ {
            headB = headB.Next
        }
    }
    
    for headA != nil {
        if headA == headB {
            return headA
        }
        headA = headA.Next
        headB = headB.Next
    }
    return nil;
}

//獲取鏈表長度
func getLen(head *ListNode) int {
    var len int
    for head != nil {
        len++
        head = head.Next
    }
    return len
}

後記

這道題也是面試題中經常遇到的“兩個鏈表的第一個公共節點”。雖然難度是“簡單”,但對我來說一點也不簡單。看評論和題解漲了很多姿勢。

而且,一開始用的是 PHP,可怎麼都通不過,不得不用 Go 重寫了一遍,倒是很快通過了。

附上 PHP 版本:

  • 題解1 PHP版本
class ListNode {
    public $val = 0;
    public $next = null;
    function __construct($val) { 
        $this->val = $val; 
    }
}

/**
 * @param ListNode $headA
 * @param ListNode $headB
 * @return ListNode
 */
function getIntersectionNode($headA, $headB) {
    $hashMap = [];
    while ($headA) {
        $hashMap[] = $headA;
        $headA = $headA->next;
    }
    while ($headB) {
        if (in_array($headB, $hashMap)) {
            return $headB;
        }
        $headB = $headB->next;
    }
    return null;
}
  • 題解2 PHP版本
function getIntersectionNode($headA, $headB) {
    if ($headA == null || $headB == null) {
        return null;
    }

    $pA = $headA;
    $pB = $headB;
    while ($pA != $pB) {
        $pA = $pA == null ? $headB : $pA->next;
        $pB = $pB == null ? $headA : $pB->next;
    }
    return $pA;
}
  • 題解3 PHP版本
function getIntersectionNode($headA, $headB) {
    $lenA = getListLength($headA);
    $lenB = getListLength($headB);

    $diffLen = $lenA > $lenB ? $lenA - $lenB : $lenB - $lenA;
    $headLong = $lenA > $lenB ? $headA : $headB;
    $headShort = $lenA > $lenB ? $headB : $headA;

    //先在長鏈表上走幾步,再同時在兩個鏈表上遍歷
    for ($i = 0; $i < $diffLen; $i++) {
        $headLong = $headLong->next;
    }
    while ($headLong != $headShort) {
        $headLong = $headLong->next;
        $headShort = $headShort->next;
    }
    return $headLong;
}

/**
 * 獲取鏈表的長度
 */
function getListLength($head)
{
    $length = 0;
    $current = $head;
    while ($current != null) {
        $length++;
        $current = $current->next;
    }
    return $length;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章