劍指Offer Java版 雙指標在連結串列中的應用
所謂雙指標,指的是在遍歷物件的過程中,不是使用單個指標進行訪問,而是使用兩個相同方向或者相反方向的指標進行遍歷,從而達到相應的目的。雙指標的使用可以降低程式的時間複雜度或者空間複雜度,總之是一種有效的解決問題的方案。
(注:這裡所說的指標,並不是C/C++中指標的概念,而是指索引,遊標或指標,可迭代物件等)
雙指標在連結串列中也有很多用處,比如前面寫到過的找出連結串列中的倒數第k個結點,就巧妙地利用到了雙指標,此外,判斷連結串列中是否有環也可以使用雙指標,設兩個快慢指標,讓快指標一次移動兩步,慢指標一次移動一步,若連結串列中有環,那麼快指標與慢指標一定能夠相遇,若兩者沒有相遇,說明連結串列中沒有環。還有如果要給出連結串列中的中間的結點,也可以使用快慢指標,讓快指標一次移動兩步,慢指標一次移動一步,當快指標剛好到達連結串列的末尾時,慢指標所指向的正好是中間的結點(對於奇數個結點,就是中間中的一個,若是偶數個結點,是中間結點中的後一個,即length
/ 2);
由此看出,雙指標的思想就是建立兩個指標,這兩個指標可以使相同方向,一般前進的速度不同或者兩者的前進順序不一致;也可能是相反的方向,通過使用相關的變數控制來達到我們的目的。
下面是幾道相關的題目:
1.給出一個連結串列,判斷是否有環
public boolean hasCycle(ListNode head) { if(head == null){ return false; } ListNode fast = head, slow = head; while(fast != null && fast.next!=null){ slow = slow.next; fast = fast.next.next; if(slow == fast){ return true; } } //注意迴圈的條件是fast不為空,並且fast.next不為空 //若只有一個頭結點,並且頭結點的next指向頭結點,仍然應該返回true return head.next == head; }
2.給出一個連結串列,若連結串列中有環,給出環的入口結點,否則返回null
若連結串列中有環,則採用快慢指標判斷時,兩者一定在環內相遇(除非環的入口在頭結點)。推導過程如下:
如果判斷出單鏈表有環,如何找到環的入口?比如上圖中,D就是環的入口。假設慢指標移動了S個結點,則快指標移動了2S個結點,如果環入口離頭結點的距離為x,環入口離相遇的那個結點距離為y,環的長度為r, 單鏈表總長度為L,他們的關係推導如下:
- 2S = S + n * r (快指標的步數等於S 加上環上多轉的n圈)
- S = n * r
- S = x + y = n * r
- x + y = (n - 1) * r + r
- x + y = (n - 1) * r + L - x
- x = (n - 1) * r + L - x - y
最後一個式子中,L - x - y為相遇點到環入口的距離,最後一個式子表明環入口到頭結點的距離等於(n - 1) * r 加上相遇點到環入口的距離。也就是說一個指標從頭結點出發,另一個指標從相遇點出發,步長都為1的話,這兩個指標一定會在環入口相遇(因為 r為環的長度,(n - 1) * r一定是環的整數倍)。
<span style="font-family:microsoft yahei;"> public ListNode detectCycle(ListNode head) {
//如果有環,則兩者一定是在環中相遇
ListNode meetNode = meetNode(head);
if(meetNode == null){
return null;
}
ListNode slow = head;
//當兩者再次相遇時,即為連結串列中環的入口
while(slow != meetNode){
slow = slow.next;
meetNode = meetNode.next;
}
return slow;
}
public ListNode meetNode(ListNode head){
if(head == null){
return null;
}
ListNode meetNode;
ListNode fast = head, slow = head;
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
if(slow == fast){
meetNode = slow;
return meetNode;
}
}
//注意迴圈的條件是fast不為空,並且fast.next不為空
//若只有一個頭結點,並且頭結點的next指向頭結點,仍然應該返回true
return head.next == head ? head : null;
}</span>
其他解法可以見這個連結,雙指標中環的入口
雙指標的應用還有還多,除了可以應用在連結串列中,還可以用在陣列中。比如求兩個數的和等於給定的數時,就可以使用雙指標來解決。以後會仔細談一談2sum(兩個數的和),3sum,4sum問題以及適用於雙指標的其他問題。
2016 年10月 8 號更新
今天刷leetcode的時候,碰到了一道happy number的題
A happy number is a number defined by the following process: Starting with any positive integer, replace the number by the sum of the squares of its digits, and repeat the process until the number equals 1 (where it will stay), or it loops endlessly in a cycle which does not include 1. Those numbers for which this process ends in 1 are happy numbers.
剛開始以為是逐漸的判斷,後來看了解析之後才明白我們同樣可以檢測是否有環(類似於連結串列中檢測有環的思路),可以看這篇文章
下面是Ac的程式碼
public boolean isHappy(int n) {
int slow = n , fast = n;
do{
slow = digitSquareSum(slow);
fast = digitSquareSum(fast);
fast = digitSquareSum(fast);
}while(slow != fast);
return slow == 1;
}
public int digitSquareSum(int n){
if(n < 0){
return -1;
}
int sum = 0;
while(n > 0){
sum += Math.pow(n % 10 , 2);
n = n / 10;
}
return sum;
}
不得不說,之前以為檢測環只能用於連結串列中,也沒想過還可以這樣使用,算是打開了思路吧