快轉到主要內容

基本雙指針

目錄

介紹
#

雙指針(Two pointer)是一種常見的技巧,概念就是用兩個變數指向陣列中不同的位置,然後根據問題的要求,移動這兩個指針。通常可以利用單調性來以更好的時間複雜度解決問題。

合併兩個排序陣列
#

一個基本的題型是,給定兩個排序過的陣列,要求合併這兩個陣列並且保持排序。

這個問題就可以使用雙指針解決!

也許你已經想到了,我們可以用兩個指針分別指向兩個陣列的開頭,然後比較這兩個位置的元素:

image

把較小的元素放入答案陣列,然後移動指向這個元素的指針:

image

接下來繼續比較兩個指針指向的元素:

image

重複這個過程直到其中一個陣列的元素全部被放入答案陣列:

image

如此一來,我們就在 \(O(n)\) 的時間複雜度內完成了這個問題。

程式碼也非常簡單:

// 雙指針主要的部分
int i = 0, j = 0;
vector<int> ans;
while (i < nums1.size() && j < nums2.size()) {
    if (nums1[i] < nums2[j]) {
        ans.push_back(nums1[i]);
        i++;
    } else {
        ans.push_back(nums2[j]);
        j++;
    }
}

// 把剩下的元素放入答案陣列
while (i < nums1.size()) {
    ans.push_back(nums1[i]);
    i++;
}

while (j < nums2.size()) {
    ans.push_back(nums2[j]);
    j++;
}

當然,從上面的例子可以發現,這個方法只能用在兩個排序過的陣列上。如果陣列沒有排序,那麼這個方法就不適用了。也就是說,大部分的雙指針問題都會依賴於陣列的單調性。

進階題
#

我們有一個長度為 \(n\) 的陣列 \(a\) 和一個長度為 \(m\) 的陣列 \(b\),在兩個陣列皆是排序的情況下,請對於每個 \(b_i\) 計算有多少陣列 \(a\) 中的元素嚴格小於 \(b_i\)。

\(1 \leq n, m \leq 10^5\),\(1 \leq a_i, b_i \leq 10^9\)。

解決問題的第一步就是觀察,我們可以在這個問題中看到什麼單調性呢?

如果 \(a\) 陣列中存在 \(k\) 個比 \(b_i\) 小的元素,那麼對於 \(b_{i+1}\) 來說,答案一定不會比 \(k\) 小。

看看下面的例子,陣列 \(b\) 中的 \(13\) 有 \(3\) 個比它小的元素:

image

那麼對於 \(b\) 中的 \(15\) 來說,答案一定不可能比 \(3\) 小:

image

根據這個性質,我們可以使用雙指針解決這個問題:

指針 \(i\) 指向陣列 \(a\) 目前比較到的位置,指針 \(j\) 指向陣列 \(b\) 目前比較到的位置,每次指針 \(j\) 往右移動時,我們就把指針 \(i\) 往右移動直到 \(a[i] \geq b[j]\),並把 \(i\) 的值當作答案記錄下來。

for (int i = 0, j = 0; j < b.size(); j++) {
    while (i < a.size() && a[i] < b[j]) {
        i++;
    }
    ans[j] = i;
}
Piau 的筆記本
作者
Piau 的筆記本
希望我寫下來的東西能夠長久的記在我的腦中