用 龜兔賽跑演算法 來檢查有沒有環路Linked List Cycle_Leetcode #141

更新於 發佈於 閱讀時間約 5 分鐘

題目敘述

給定一個Linked list鏈結串列的Head node,

請判斷這條Linked list是否存在環路(Cycle)?

如果有環路,回傳True。

如果沒有,回傳False。


題目的原文敘述


測試範例

Example 1:

raw-image


Input: head = [3,2,0,-4], pos = 1
Output: true
Explanation: There is a cycle in the linked list, where the tail connects to the 1st node (0-indexed).
有環路Cycle​


Example 2:

raw-image


Input: head = [1,2], pos = 0
Output: true
Explanation: There is a cycle in the linked list, where the tail connects to the 0th node.
有環路Cycle


Example 3:

raw-image


Input: head = [1], pos = -1
Output: false
Explanation: There is no cycle in the linked list.
沒有環路 

約束條件

Constraints:

  • The number of the nodes in the list is in the range [0, 10^4].

Linked list的節點總數目介於 0 ~ 一萬之間。

  • -10^5 <= Node.val <= 10^5

每個節點的節點值都介於 負十萬~正十萬 之間。

  • pos is -1 or a valid index in the linked-list.

pos代表發生環路的節點編號,如果是-1代表沒有環路。


進階提問

Follow up: Can you solve it using O(1) (i.e. constant) memory?

能不能在常數空間複雜度的條件下,解出這題?


演算法 雙指針(快/慢 指針) 俗稱龜兔賽跑演算法

有一個比較直覺的想法,就是沿路記下每個節點的ID,在C/C++裡面可以記錄節點的記憶體位置,在python裡面可以記錄run-time的物件ID。如果在拜訪的過程中,遇到相同的ID,就代表有環路。

但是,這個方法會耗費O(n)的空間成本去紀錄節點的ID,有沒有更好的方法呢?

其實有喔!


(俗稱龜兔賽跑演算法,或者Floyd cycle detection algorithm)

想一下,假如有環路發生,我們可以說linked list中的某一塊形成了封閉的環型,就好像操場跑道的形狀

我們讓兩個跑者(在演算法裡面就是雙指針,快/慢指針),同時從起點出發,快的固定都跑得比慢的還快,最後快的一定可以領先一圈,又遇到慢的跑者




相反的,如果沒有環路,相當於linked list是一條直線型的跑道,那麼我們讓兩個跑者同時從起點出發,兩者並不會在中途相遇


為了方便實作,我們可以假設快跑者是慢跑者的兩倍速度,讓他們同時從起點出發

假如快、慢跑者中途會相遇,那麼代表linked list 存在至少一個環路
假如快、慢跑者不會相遇,那麼代表linked list是一條直線的串列,沒有環路

這樣我們只要維護兩個pointer 快、慢指針就相當於快、慢跑者,
只需要常數空間O(1)即可。


程式碼 雙指針(快/慢 指針) 俗稱龜兔賽跑演算法

class Solution:
def hasCycle(self, head: ListNode) -> bool:

# two pointers
slow, fast = head, head

# slow runner move 1 step, fast runner move 2 steps per iteration
# if fast runner meets slow one somewhere, then there is a cycle in linked list

while fast and fast.next:

slow, fast = slow.next, fast.next.next

if slow is fast:
return True

return False

複雜度分析

時間複雜度:

需要掃描過每個節點,所需時間為O(n)。

空間複雜度:

只需要維護固定尺寸的臨時變數,所需空間為O(1) 常數級別。


關鍵知識點

假如有環路發生,我們可以說linked list中的某一塊形成了封閉的環型,就好像操場跑道的形狀

我們讓兩個跑者(在演算法裡面就是雙指針,快/慢指針),同時從起點出發,快的固定都跑得比慢的還快,最後快的一定可以領先一圈,又遇到慢的跑者


相反的,如果沒有環路,相當於linked list是一條直線型的跑道,那麼我們讓兩個跑者同時從起點出發,兩者並不會在中途相遇


Reference:

[1] Linked List Cycle - LeetCode

avatar-img
91會員
425內容數
由有業界實戰經驗的演算法工程師, 手把手教你建立解題的框架, 一步步寫出高效、清晰易懂的解題答案。 著重在讓讀者啟發思考、理解演算法,熟悉常見的演算法模板。 深入淺出地介紹題目背後所使用的演算法意義,融會貫通演算法與資料結構的應用。 在幾個經典的題目融入一道題目的多種解法,或者同一招解不同的題目,擴展廣度,並加深印象。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
這篇文章,會帶著大家複習以前學過的遞回框架, 並且鏈結串列的概念與應用為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 遞回框架 尋找共通模式(common pattern),對應到演算法的General case 確立初始條件(initial conditio
題目敘述 題目會給定一個鏈結串列的起始點,要求我們把其中區間總和為0的部分刪除掉。 例如 1→ 2 → -2 → 3 → 4 裡面有一段是2 → -2 區間總和為零,所以簡化刪除後變成 1→ 3 → 4 題目的原文敘述 測試範例 Example 1: Input: head
題目敘述 題目會給我們一個鏈結串列的起點head,要求我們找出這個串列的中點。 註: 如果串列長度是偶數,就回傳中間偏右的那個節點。 例如: 1 -> 2 -> 3 回傳中點為2 1 -> 2 -> 3 -> 4 ->5 -> 6 回傳中點為4 詳細的題目可在這裡看到 測試範例
題目會給定一個帶有Random Pointer的鏈結串列,要求我們實體複製deep copy這條鏈結串列,並且輸出副本的根結點。
題目會給定我們一個串列,和一個n值,要求我們刪除尾巴數來的第n個節點。 例如 1->2->3->4->5 和 給定n值=2,要求我們刪除尾巴數來的第2個節點。 尾巴數來的第2個節點是4,刪除之後,更新連結,輸出答案如下 1->2->3->5
題目會給我們兩條已經從小到大排序好的串列,要求我們依照從小到大的順序,合併這兩條串列。
這篇文章,會帶著大家複習以前學過的遞回框架, 並且鏈結串列的概念與應用為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 遞回框架 尋找共通模式(common pattern),對應到演算法的General case 確立初始條件(initial conditio
題目敘述 題目會給定一個鏈結串列的起始點,要求我們把其中區間總和為0的部分刪除掉。 例如 1→ 2 → -2 → 3 → 4 裡面有一段是2 → -2 區間總和為零,所以簡化刪除後變成 1→ 3 → 4 題目的原文敘述 測試範例 Example 1: Input: head
題目敘述 題目會給我們一個鏈結串列的起點head,要求我們找出這個串列的中點。 註: 如果串列長度是偶數,就回傳中間偏右的那個節點。 例如: 1 -> 2 -> 3 回傳中點為2 1 -> 2 -> 3 -> 4 ->5 -> 6 回傳中點為4 詳細的題目可在這裡看到 測試範例
題目會給定一個帶有Random Pointer的鏈結串列,要求我們實體複製deep copy這條鏈結串列,並且輸出副本的根結點。
題目會給定我們一個串列,和一個n值,要求我們刪除尾巴數來的第n個節點。 例如 1->2->3->4->5 和 給定n值=2,要求我們刪除尾巴數來的第2個節點。 尾巴數來的第2個節點是4,刪除之後,更新連結,輸出答案如下 1->2->3->5
題目會給我們兩條已經從小到大排序好的串列,要求我們依照從小到大的順序,合併這兩條串列。
你可能也想看
Google News 追蹤
Thumbnail
大家好,我是woody,是一名料理創作者,非常努力地在嘗試將複雜的料理簡單化,讓大家也可以體驗到料理的樂趣而我也非常享受料理的過程,今天想跟大家聊聊,除了料理本身,料理創作背後的成本。
Thumbnail
哈囉~很久沒跟各位自我介紹一下了~ 大家好~我是爺恩 我是一名圖文插畫家,有追蹤我一段時間的應該有發現爺恩這個品牌經營了好像.....快五年了(汗)時間過得真快!隨著時間過去,創作這件事好像變得更忙碌了,也很開心跟很多厲害的創作者以及廠商互相合作幫忙,還有最重要的是大家的支持與陪伴🥹。  
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
for loop、while loop、repeat
Thumbnail
[路] 或許 不是前方沒有路 也許 只是還沒跨出 那一步
※ 何謂巢狀迴圈(NESTD LOOP): 指的是一個迴圈內包含另一個迴圈的結構。在程式設計中,這種結構常用於需要進行多層次迭代的場合,例如處理多維數組、逐行逐列處理表格資料等。 ※ 例子:九九乘法表 說明: 外層迴圈:for (let i = 1; i <= 9; i = i + 1) 這
※ 迴圈(for loop)介紹: 迴圈的用途是重複執行程式碼,只要條件滿足,就會執行特定的動作。 for (let i = 0; i < 10; i = i + 1) { console.log(i); } 說明: for:對於。 let:因為迭代器的數值會一直改變所以要用let
Thumbnail
Node 對我來說一直是很似懂非懂的概念,因為沒有實際可見的數字或字串實物可以觀察中間步驟,比起其他解題抽象許多。以下解題方法來自網友與 GPT 提供的答案,我想在本文中嘗試自己做視覺化圖解,說明 CodeWars - Can you get the loop ? 的解題思路。
Thumbnail
for,while,do while語法介紹 for loop for比較偏向固定圈數型的迴圈 語法 for(計數變數初值; 布林運算式 ; 增量運算) { : 一般指令; : }
Thumbnail
巢狀迴圈For loop介紹結構及範例說明 巢狀迴圈 巢狀迴圈是在一個迴圈內包含另一個迴圈的結構 簡單來說,就是內迴圈做完,才會在跑到外迴圈,接著在做內迴圈
Thumbnail
大家好,我是woody,是一名料理創作者,非常努力地在嘗試將複雜的料理簡單化,讓大家也可以體驗到料理的樂趣而我也非常享受料理的過程,今天想跟大家聊聊,除了料理本身,料理創作背後的成本。
Thumbnail
哈囉~很久沒跟各位自我介紹一下了~ 大家好~我是爺恩 我是一名圖文插畫家,有追蹤我一段時間的應該有發現爺恩這個品牌經營了好像.....快五年了(汗)時間過得真快!隨著時間過去,創作這件事好像變得更忙碌了,也很開心跟很多厲害的創作者以及廠商互相合作幫忙,還有最重要的是大家的支持與陪伴🥹。  
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
for loop、while loop、repeat
Thumbnail
[路] 或許 不是前方沒有路 也許 只是還沒跨出 那一步
※ 何謂巢狀迴圈(NESTD LOOP): 指的是一個迴圈內包含另一個迴圈的結構。在程式設計中,這種結構常用於需要進行多層次迭代的場合,例如處理多維數組、逐行逐列處理表格資料等。 ※ 例子:九九乘法表 說明: 外層迴圈:for (let i = 1; i <= 9; i = i + 1) 這
※ 迴圈(for loop)介紹: 迴圈的用途是重複執行程式碼,只要條件滿足,就會執行特定的動作。 for (let i = 0; i < 10; i = i + 1) { console.log(i); } 說明: for:對於。 let:因為迭代器的數值會一直改變所以要用let
Thumbnail
Node 對我來說一直是很似懂非懂的概念,因為沒有實際可見的數字或字串實物可以觀察中間步驟,比起其他解題抽象許多。以下解題方法來自網友與 GPT 提供的答案,我想在本文中嘗試自己做視覺化圖解,說明 CodeWars - Can you get the loop ? 的解題思路。
Thumbnail
for,while,do while語法介紹 for loop for比較偏向固定圈數型的迴圈 語法 for(計數變數初值; 布林運算式 ; 增量運算) { : 一般指令; : }
Thumbnail
巢狀迴圈For loop介紹結構及範例說明 巢狀迴圈 巢狀迴圈是在一個迴圈內包含另一個迴圈的結構 簡單來說,就是內迴圈做完,才會在跑到外迴圈,接著在做內迴圈