(Day9)演算法復健運動for藍橋杯-雙指標

wlqtc發表於2024-06-25

(Day9)演算法復健運動for藍橋杯-雙指標

先粘一個部落格:素材來源https://www.cnblogs.com/luoyj/p/12408871.html

尺取法(又稱為:雙指標、two pointers)是演算法競賽中一個常用的最佳化技巧,用來解決序列的區間問題,操作簡單、容易程式設計。
如果區間是單調的,也常常用二分法來求解,所以很多問題用尺取法和二分法都行。
另外,尺取法的的操作過程和分治演算法的步驟很相似,有時候也用在分治中。
要點:
(1)一般序列都是有序的,無序的可以先排序。
(2)問題和序列的區間有關,且需要操作2個變數,可以用兩個下標(指標)i、j掃描區間。
兩個方向:
(a)反向掃描。i、j方向相反,i從頭到尾,j從尾到頭,在中間相會。
(b)同向掃描。i、j方向相同,都從頭到尾,可以讓j跑在i前面。
  在leetcode的一篇文章中 常用的雙指標技巧 https://leetcode-cn.com/circle/article/GMopsy/,把同向掃描的i、j指標稱為“快慢指標”,把反向掃描的i、j指標稱為“左右指標”,更加形象。快慢指標在序列上產生了一個大小可變的“滑動視窗”,有靈活的應用,例如3.1的“尋找區間和”問題。

反向掃描

例題1:找一個序列其中兩個數加起來為m。(序列是有序的)

模板程式碼:

void find_sum(int a[], int n, int m)
{
    sort(a, a + n - 1); //先排序,複雜度O(nlogn) 
    int i = 0, j = n - 1; //i指向頭,j指向尾 
    while (i < j)//複雜度O(n)
    { 
        int sum = a[i] + a[j]; 
        if (sum > m) j--; 
        if (sum < m) i++;
        if (sum == m)
        { 
            cout << a[i] << " " << a[j] << endl; //列印一種情況 
            i++; //可能有多個答案,繼續 
        } 
    } 
}

在這個題目中,尺取法不僅效率高,而且不需要額外的空間。
把題目的條件改變一下,可以變化為類似的問題,例如:判斷一個數是否為兩個數的平方和
判斷迴文串:
這個自己想,一個從第一個開始跑,一個從最後一個開始跑

同向掃描

這是用尺取法產生“滑動視窗”的典型例子。
∎問題描述
  給定一個長度為n的陣列a[]和一個數s,在這個陣列中找一個區間,使得這個區間之和等於s。輸出區間的起點和終點位置。
  樣例輸入:
  15
  6 1 2 3 4 6 4 2 8 9 10 11 12 13 14
  6
  樣例輸出:
  0 0
  1 3
  5 5
  6 7
  說明:樣例輸入的第1行是n=15,第2行是陣列a[],第3行是區間和s=6。樣例輸出,共有4個情況。
∎題解
  指標i和j,i<=j,都從頭向尾掃描,判斷區間[i,j]的和是否等於s。
  如何尋找區間和等於s的區間?如果簡單地對i和j做二重迴圈,複雜度是O(n2)。用尺取法,複雜度O(n),操作步驟是:
  (1)初始值i=0、j=0,即開始都指向第一個元素a[0]。定義sum是區間[i, j]的和,初始值sum = a[0]。
  (2)如果sum等於s,輸出一個解。繼續,把sum減掉元素a[i],並把i往後移動一位。
  (3)如果sum大於s,讓sum減掉元素a[i],並把i往後移動一位。
  (4)如果sum小於s,把j往後挪一位,並把sum的值加上這個新元素。
  在上面的步驟中,有2個關鍵技巧:
  (1)滑動視窗的實現。視窗就是區間[i,j],隨著i和j從頭到尾移動,視窗就“滑動”掃描了整個序列,檢索了所有的資料。i和j並不是同步增加的,視窗像一隻蚯蚓伸縮前進,它的長度是變化的,這個變化,正對應了對區間和的計算。
  (2)sum的使用。如何計算區間和?暴力的方法是從a[i]到a[j]累加,但是,這個累加的複雜度是O(n)的,會超時。如果利用sum,每次移動i或j的時候,只需要把sum加或減一次,就得到了區間和,複雜度是O(1)。這是“字首和”遞推思想的應用。
  下面是程式碼。

int i = 0, j = 0;
int sum = a[0];
while(j < n)
{   //下面程式碼中保證 i<=j
    if(sum >= s)
    {
        if(sum == s) 
        printf("%d %d\n", i, j);
        sum -= a[i];
        i++;
        if(i>j) 
        {
            sum = a[i];
            j++;
        }  //防止i超過j
    }
    if(sum < s)
    {
        j++;
        sum += a[j];
    }
}

“滑動視窗”的例子還有:
  (1)給定一個序列,以及一個整數M;在序列中找M個連續遞增的元素,使它們的區間和最大。
  (2)給定一個序列,以及一個整數K;求一個最短的連續子序列,其中包含至少K個不同的元素。
  在“4 典型題目”中有相似的題目。
陣列去重
很簡單,自己想

相關文章