Leetcode141題和142題很相似,都涉及到了快慢指針的算法。快慢指針就是設置兩個指針,一個快指針,一個慢指針來達到解題的目的。下面分爲以下幾個模塊來講解:
- 解第141題
- 解第142題
- 爲什麼快指針一定要設置爲慢指針的2倍
- 快慢指針的拓展應用
一、Leetcode 141題
題目爲:Linked List Cycle
Given a linked list, determine if it has a cycle in it.
Follow up:
Can you solve it without using extra space?
解法思想:
算法思想就是設置一個快指針fp和一個慢指針sp,兩個指針起始同時指向head節點,其中快指針每次走兩步,慢指針每次走一步,那麼如果鏈表有環的話他們一定能夠相遇。可以想象兩個人同時從操場上起跑,其中甲的速度是乙速度的2倍,那麼當乙跑完一圈的時候甲也跑了兩圈,他們一定能夠相遇。
下面看具體的Java代碼:
public class Solution {
public boolean hasCycle(ListNode head) {
if( head == null || head.next == null ){
return false;
}
// 快指針fp和慢指針sp,
ListNode fp = head, sp = head;
while( fp != null && fp.next != null){
sp = sp.next;
fp = fp.next.next;
if( fp == sp ){
return true;
}
}
return false;
}
}
二、Leetcode 142題
題目爲:Linked List Cycle II
Given a linked list, return the node where the cycle begins. If there is no cycle, return null.
Note: Do not modify the linked list.
Follow up:
Can you solve it without using extra space?
解法思想:
主要思想和上一個算法前面基本一樣,也是設置一個快指針fp和一個慢指針sp,不同的是要求出如果鏈表存在環,那麼求出環入口的起始結點。接下來我們就是要從上面的算法中做一定程度的改變就可以完成題目的要求了。
我首先說一下具體的解法,推導過程後面再說。
如果鏈表中存在環,那麼fp和sp一定會相遇,當兩個指針相遇的時候,我們設相遇點爲c,此時fp和sp都指向了c,接下來令fp繼續指向c結點,sp指向鏈表頭結點head,此時最大的不同是fp的步數變成爲每次走一步,令fp和sp同時走,每次一步,那麼它們再次相遇的時候即爲環的入口結點。
接下來我們也許想知道爲什麼fp和sp指向上面所說的結點之後,同時以步數前進,再次相遇的時候一定會指向環的入口結點?
推導:
問題可以轉化成求c到d的距離cd爲什麼等於h到d的距離hd?
首先如圖所示,鏈表的整個長度爲L,鏈表頭head爲h,假設fp和sp按照箭頭所示的方向走。其中環入口點爲d,h到d的距離hd爲a。fp和sp假設初次相遇在c,初次相遇的時候慢指針sp肯定沒有走完整個鏈表。設d到c的距離dc爲x,h到c的距離即爲sp初次相遇所走的路程,即設hc長度爲s。此外設環的長度爲r。而在fp和sp初次相遇在c點的時候,fp則在環內已經走了n圈。由於fp的速度是sp的2倍,接下來我們可以得出:
- f = 2s
- f = s + nr
nr 表示在s與f相遇時,f多走了n圈,nr爲走的總長度。
由1和2 得到 : s = nr (1)
又因爲hd距離爲a,dc距離爲x,hc距離爲s,所以可以得出:
a + x = s (2)
結合(1)和(2)可以得到:
a + x = nr -> a + x = (n-1)r + r -> a + x = (n-1)r + (L-a) 註釋:L-a即爲環長r
-> a = (n-1)r + (L-a-x)
即此時h到d的距離a等於c到d的距離(L-a-x)。所以當fp和sp初次相遇在c點的時候,令fp從c點出發,sp指向
鏈表頭h,兩個同時以步數爲1同時出發,則再次相遇的時候即爲環的入口節點d。
接下來看具體的java代碼:
public class Solution {
public ListNode detectCycle( ListNode head ) {
if( head == null || head.next == null ){
return null;
}
// 快指針fp和慢指針sp,
ListNode fp = head, sp = head;
while( fp != null && fp.next != null){
sp = sp.next;
fp = fp.next.next;
//此處應該用fp == sp ,而不能用fp.equals(sp) 因爲鏈表爲1 2 的時候容易
//拋出異常
if( fp == sp ){ //說明有環
break;
}
}
//System.out.println( fp.val + " "+ sp.val );
if( fp == null || fp.next == null ){
return null;
}
//說明有環,求環的起始節點
sp = head;
while( fp != sp ){
sp = sp.next;
fp = fp.next;
}
return sp;
}
}
三、爲什麼快指針一定要設置爲慢指針的2倍
看了上面的題解也許你會問問什麼快指針一定要設置爲慢指針的2倍呢,爲什麼不是3倍。例如兩個人同時從操場出發,其中甲的速度是乙的三倍,當乙跑完一圈的時候,甲跑了三圈,不也是可以相遇的嗎。
接下來我會推導,設快指針fp的速度是慢指針速度sp的t倍,其他參數還和上面圖片即解釋一致。我們可以得出:
ts = s + nr
-> (t-1)s = nr (3)
由(2)和(3)可以得到:
(t-1)(a+x) = nr
-> (t-1)(a+x) = (n-1)r + r
-> (t-1)(a+x) = (n-1)r + (L-a)
-> a = (n-1)/(t-1)*r + [(L-a)/(t-1) - x]
其中a必定爲整數,因爲a代表的是h到d的結點個數。所以(n-1)/(t-1)和(L-a)/(t-1) 也須爲整數,所以當t爲2的時候,一定爲整數,當然t也可以爲其他,只是遇到鏈表不一樣的情況時候所得到的a不一定爲整數。
四、快慢指針的拓展應用
目前我遇到的主要有以下幾點應用:
如上面第一題,判斷一個鏈表是否有環
如上面第二題,求一個鏈表是否存在環,如果存在,則求出環的入口結點
另一個應用是求鏈表是否存在環的變式,如給定兩個鏈表A和B,判斷兩個鏈表是否相交,解決方法就是將A鏈表尾節點指向頭結點形成一個環,檢測B鏈表是否存在環,如果存在,則兩個鏈表相交,而檢測出來的依賴環入口即爲相交的第一個點。
求有序鏈表中求出其中位數,這種問題也是設置快慢指針,當快指針到底鏈表尾部的時候,慢指針剛好指向鏈表中間的結點。
以上就是Leetcode兩道題的解法和快慢指針的詳細解釋。如果有什麼問題可以指出來一起探討,當然如果覺得不錯,可以頂一下,謝謝!
文章來源:https://yq.aliyun.com/articles/27081