雙指針技巧彙總

我認爲雙指針技巧還能夠分爲兩類,一類是「快慢指針」,另外一類是「左右指針」。前者解決主要解決鏈表中的問題,好比典型的斷定鏈表中是否包含環;後者主要解決數組(或者字符串)中的問題,好比二分查找。html

1、快慢指針的常見算法

快慢指針通常都初始化指向鏈表的頭結點 head,前進時快指針 fast 在前,慢指針 slow 在後,巧妙解決一些鏈表中的問題。java

1、斷定鏈表中是否含有環

這應該屬於鏈表最基本的操做了,若是讀者已經知道這個技巧,能夠跳過。算法

單鏈表的特色是每一個節點只知道下一個節點,因此一個指針的話沒法判斷鏈表中是否含有環的。數組

若是鏈表中不包含環,那麼這個指針最終會遇到空指針 null 表示鏈表到頭了,這還好說,能夠判斷該鏈表不含環。框架

boolean hasCycle(ListNode head) {
    while (head != null)
        head = head.next;
    return false;
}

可是若是鏈表中含有環,那麼這個指針就會陷入死循環,由於環形數組中沒有 null 指針做爲尾部節點。指針

經典解法就是用兩個指針,一個每次前進兩步,一個每次前進一步。若是不含有環,跑得快的那個指針最終會遇到 null,說明鏈表不含環;若是含有環,快指針最終會超慢指針一圈,和慢指針相遇,說明鏈表含有環。code

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;
}

二、已知鏈表中含有環,返回這個環的起始位置

這個問題其實不困難,有點相似腦筋急轉彎,先直接看代碼:htm

ListNode detectCycle(ListNode head) {
    ListNode fast, slow;
    fast = slow = head;
    while (fast != null && fast.next != null) {
        fast = fast.next.next;
        slow = slow.next;
        if (fast == slow)
            break;
    }
    
    slow = head;
    while (slow != fast) {
        fast = fast.next;
        slow = slow.next;
    }
    return slow;
}

能夠看到,當快慢指針相遇時,讓其中任一個指針從新指向頭節點,而後讓它倆以相同速度前進,再次相遇時所在的節點位置就是環開始的位置。這是爲何呢?blog

第一次相遇時,假設慢指針 slow 走了 k 步,那麼快指針 fast 必定走了 2k 步,也就是說比 slow 多走了 k 步(也就是環的長度)。排序

設相遇點距環的起點的距離爲 m,那麼環的起點距頭結點 head 的距離爲 k - m,也就是說若是從 head 前進 k - m 步就能到達環起點。

巧的是,若是從相遇點繼續前進 k - m 步,也剛好到達環起點。

因此,只要咱們把快慢指針中的任一個從新指向 head,而後兩個指針同速前進,k - m 步後就會相遇,相遇之處就是環的起點了。

3、尋找鏈表的中點

相似上面的思路,咱們還可讓快指針一次前進兩步,慢指針一次前進一步,當快指針到達鏈表盡頭時,慢指針就處於鏈表的中間位置。

ListNode slow, fast;
slow = fast = head;
while (fast != null && fast.next != null) {
    fast = fast.next.next;
    slow = slow.next;
}
// slow 就在中間位置
return slow;

當鏈表的長度是奇數時,slow 恰巧停在中點位置;若是長度是偶數,slow 最終的位置是中間偏右:

尋找鏈表中點的一個重要做用是對鏈表進行歸併排序。

回想數組的歸併排序:求中點索引遞歸地把數組二分,最後合併兩個有序數組。對於鏈表,合併兩個有序鏈表是很簡單的,難點就在於二分。

可是如今你學會了找到鏈表的中點,就能實現鏈表的二分了。關於歸併排序的具體內容本文就不具體展開了。

4、尋找鏈表的倒數第 k 個元素

咱們的思路仍是使用快慢指針,讓快指針先走 k 步,而後快慢指針開始同速前進。這樣當快指針走到鏈表末尾 null 時,慢指針所在的位置就是倒數第 k 個鏈表節點(爲了簡化,假設 k 不會超過鏈表長度):

ListNode slow, fast;
slow = fast = head;
while (k-- > 0) 
    fast = fast.next;

while (fast != null) {
    slow = slow.next;
    fast = fast.next;
}
return slow;

2、左右指針的經常使用算法

左右指針在數組中實際是指兩個索引值,通常初始化爲 left = 0, right = nums.length - 1 。

一、二分查找

前文 二分查找算法詳解 有詳細講解,這裏只寫最簡單的二分算法,旨在突出它的雙指針特性:

int binarySearch(int[] nums, int target) {
    int left = 0;
    int right = nums.length - 1;
    while(left <= right) {
        int mid = (right + left) / 2;
        if (nums[mid] == target)
            return mid;
        else if (nums[mid] < target)
            left = mid + 1;
        else if (nums[mid] > target)
            right = mid - 1;
    }
}

二、兩數之和

直接看一道 LeetCode 題目吧:

只要數組有序,就應該想到雙指針技巧。這道題的解法有點相似二分查找,經過調節 left 和 right 能夠調整 sum 的大小:

 

3、反轉數組

void reverse(int[] nums) {
    int left = 0;
    int right = nums.length - 1;
    while (left < right) {
        // swap(nums[left], nums[right])
        int temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
        left++;
        right--;
    }
}

4、滑動窗口算法

這也許是雙指針技巧的最高境界了,若是掌握了此算法,能夠解決一大類子字符串匹配的問題,不過「滑動窗口」算法比上述的這些算法稍微複雜些。

幸運的是,這類算法是有框架模板的,下篇文章就準備講解「滑動窗口」算法模板,幫你們秒殺幾道 LeetCode 子串匹配的問題。

相關文章
相關標籤/搜索