1. 程式人生 > >【LeetCode題解】141_環形連結串列

【LeetCode題解】141_環形連結串列

目錄

141_環形連結串列

描述

給定一個連結串列,判斷連結串列中是否有環。

進階: 你能否不使用額外空間解決此題?

解法一:雜湊表

思路

判斷一個連結串列是否包含環,可以轉化為判斷是否有一個節點之前已經出現過。非常自然的一個想法就是:遍歷連結串列的每個節點,用一個雜湊表記錄每個節點的引用(或記憶體地址);如果能夠遍歷到空節點,則此時已經遍歷到連結串列的尾部,返回 false;如果有一個節點的引用出現在雜湊表中,則返回 true

Java 實現

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        Set<ListNode> nodesSeen = new HashSet<>();
        while (head != null) {
            if (nodesSeen.contains(head)) {
                return true;
            }
            nodesSeen.add(head);
            head = head.next;
        }
        return false;
    }
}

複雜度分析:

  • 時間複雜度:\(O(n)\),其中 \(n\) 為連結串列的節點數,因為雜湊表的查詢和新增操作的時間複雜度是 \(O(1)\) 的,所以整體的時間複雜度是 \(O(n)\)
  • 空間複雜度:\(O(n)\),最多隻需要儲存 \(n\) 個節點的引用

Python 實現

# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution(object):
    def hasCycle(self, head):
        """
        :type head: ListNode
        :rtype: bool
        """
        nodes_seen = set()
        while head is not None:
            if head in nodes_seen:
                return True
            nodes_seen.add(head)
            head = head.next
        return False

複雜度分析同上。

解法二:雙指標(龜兔演算法)

思路

另一種思路就是採用 Floyd龜兔演算法,即借用兩個指標,一個快指標和一個慢指標,快指標每次移動兩個節點而慢指標則每次移動一個節點。如果連結串列中不存在環,則快指標會先於慢指標到達連結串列的尾部,兩個指標永遠也不可能“相遇”;相反,如果連結串列中存在環,則快指標一定會“追上”慢指標,就如同龜兔賽跑一樣,速度快的兔子一定會追上烏龜。

Java 實現

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        if (head == null || head.next == null) {
            return false;
        }
        ListNode slow = head;
        ListNode fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            
            if (slow == fast) {
                return true;
            }
        }
        return false;
    }
}

複雜度分析:

  • 時間複雜度:\(O(n)\),其中 \(n\) 為連結串列的節點數,按照連結串列中是否存在環,分兩種情況進行討論:
    1. 連結串列中不存在環:快指標會優先到達連結串列尾部(最多隻需要 \(n/2\) 次),時間複雜度為 \(O(n)\)
    2. 連結串列中存在環:我們可以把慢指標的移動分解成兩個部分,“直線“部分和”環形“部分。對於”直線“部分,假設連結串列中”直線“部分的長度為 \(N\),則慢指標只需 \(N\) 次迭代便可到達”環形“部分,此時快指標已經進入了”環形“部分;對於”環形“部分,假設連結串列中”環形“部分的長度為 \(K\),由於兩個指標的速度之差為 1,因此最壞的情況下,經過 \(K\) 次迭代後快指標便能”追上“慢指標。綜上,最壞情況下的時間複雜度為 \(O(N + K) = O(n)\)
  • 空間複雜度:\(O(1)\),只需要儲存兩個節點的引用

Python 實現

# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution(object):
    def hasCycle(self, head):
        """
        :type head: ListNode
        :rtype: bool
        """
        if head is None or head.next is None:
            return False
        
        slow, fast = head, head
        while fast is not None and fast.next is not None:
            slow, fast = slow.next, fast.next.next
            if slow == fast:
                return True
        return False
# Runtime: 44 ms
# Your runtime beats 95.91 % of python submissions.

複雜度分析同上。