快慢指針法:
快慢指針一般都初始化指向鏈表的頭結點 head,前進時快指針 fast 在前,慢指針 slow 在后,巧妙解決一些鏈表中的問題。
1.判定鏈表中是否含有環(leetcode141.環形鏈表)
這應該屬於鏈表最基本的操作了,單鏈表的特點是每個節點只知道下一個節點,所以一個指針的話無法判斷鏈表中是否含有環的。
如果鏈表中不含環,那么這個指針最終會遇到空指針 null 表示鏈表到頭了
但是如果鏈表中含有環,那么這個指針就會陷入死循環,因為環形數組中沒有 null 指針作為尾部節點。
經典解法就是用兩個指針,一個每次前進兩步,一個每次前進一步。如果不含有環,跑得快的那個指針最終會遇到 null,說明鏈表不含環;如果含有環,快指針最終會超慢指針一圈,和慢指針相遇,說明鏈表含有環。
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode fast,slow;
fast=slow=head;
while(fast!=null && fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(fast==slow){
return true;
}
}
return false;
}
}
# 這里寫法要注意的是因為fast快,所有如果無環,那么肯定是fast先碰到null
# 另外因為fast=fast.next.next; 所以fast本身可能是null,fast.next也可能是null
2.已知鏈表中含有環,返回這個環的起始位置
當快慢指針相遇時,讓其中任一個指針重新指向頭節點,然后讓它倆以相同速度前進,再次相遇時所在的節點位置就是環開始的位置。
第一次相遇時,假設慢指針 slow 走了 k 步,那么快指針 fast 一定走了 2k 步,也就是說比 slow 多走了 k 步(也就是環的長度)
設相遇點距環的起點的距離為 m,那么環的起點距頭結點 head 的距離為 k - m,也就是說如果從 head 前進 k - m 步就能到達環起點,巧的是,如果從相遇點繼續前進 k - m 步,也恰好到達環起點。
所以,只要我們把快慢指針中的任一個重新指向 head,然后兩個指針同速前進,k - m 步后就會相遇,相遇之處就是環的起點了。
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head, slow = head;
//獲取首次相遇時候,slow的位置
while(fast!= null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
break;
}
}
//如果快指針走到盡頭,沒環
if(fast == null || fast.next == null) return null;
//快指針重新出發,相遇位置就是入口位置
fast = head;
while(fast != slow){
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
//之所以要在第一個循環結束之后再判斷if(fast == null || fast.next == null) return null;
//是因為可能存在鏈表中只有一個值,那么相當於根本沒有進行第一個循環
//這行就是為了處理這個特殊的可能性
3.尋找鏈表的中點(尋找鏈表中點的一個重要作用是對鏈表進行歸並排序)
我們還可以讓快指針一次前進兩步,慢指針一次前進一步,當快指針到達鏈表盡頭時,慢指針就處於鏈表的中間位置。
當鏈表的長度是奇數時,slow 恰巧停在中點位置;如果長度是偶數,slow 最終的位置是中間偏右:
class Solution {
public ListNode middleNode(ListNode head) {
if(head==null){return null;}
ListNode fast,slow;
fast=slow=head;
while(slow!=null){
if(fast.next==null){
return slow;
}
if(fast.next.next==null){
return slow.next;
}
slow=slow.next;
fast=fast.next.next;
}
return null;
}
}
以上是我的代碼,雖然是分類討論了,但要注意分類討論完成之后還是要找共性,盡量合並的寫出
class Solution {
public ListNode middleNode(ListNode head) {
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
}
4.尋找鏈表倒數第k個元素
思路還是使用快慢指針,讓快指針先走 k 步,然后快慢指針開始同速前進。這樣當快指針走到鏈表末尾 null 時,慢指針所在的位置就是倒數第 k 個鏈表節點(為了簡化,假設 k 不會超過鏈表長度):
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode former = head, latter = head;
for(int i = 0; i < k; i++)
former = former.next;
while(former != null) {
former = former.next;
latter = latter.next;
}
return latter;
}
}