1. 程式人生 > 其它 >雙指標TwoPoints(尺取)入門與實戰

雙指標TwoPoints(尺取)入門與實戰

技術標籤:筆記演算法

方便自己預習也幫大佬們複習

概念:
《雙指標》是白鹿澄在晉江文學網上釋出的網路小說個人理解雙指標是在一個數組或連結串列中通過移動兩個指標(有人愛說左右指標也有人愛說快慢指標)選取其內部的子區間,來滿足某種條件的做法。
過程:
已知某種要求,要選取合適的子區間,可用快指標首先跑到合適的上界再不斷改變慢指標的位置來改變快慢指標之間的子區間長度,也可同時移動快慢指標,但其中要注意題目要求保持成立。
要求:
就目前入門情況看只需要
1.快慢指標左右關係不變。
2.能選取的子區間都必須滿足題目要求。


目前題型不多,會慢慢更新
下列程式碼均為AC程式碼,請放心食用

雙指標在查詢區間

時是一種便捷高效的演算法,某種程度上雖然也算暴力,但卻能將普通暴力所造成的O(nx)簡化為O(n)。

經典入門題

POJ3320測試連結

題意簡述:
有一個數組,求能包含陣列內所有出現過元素的子區間最短長度
(這個意思的題十分入門十分經典,不同的地方都會按這個題意設定故事背景來進行改變,簡而言之換湯不換藥)

輸入描述:
單例項
第一行輸入正整數n
第二行輸入元素個數為n的陣列
(1 ≤ n ≤ 1000000)

輸出描述:
輸出一個n代表求得子區間長度

樣例:
輸入:
5
1 8 8 8 1
輸出:
2
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

思路:
1.右指標跑到全部元素都涵蓋的位置
2.不斷縮排左指標
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstdlib>
#include <cstring>
#include <map>
#include <set>
using namespace std;
const
int maxn = 1000010; set<int> cnt; //記錄一共多少個不同的元素 map<int, int> each_cnt; //記錄一個元素出現多少次 int a[maxn]; int main() { each_cnt.clear(); cnt.clear(); //個人習慣先倒下垃圾 int n; scanf("%d", &n); for (int i = 0; i < n; i++) { scanf("%d", &a[i]); cnt.insert(a[i]); } int all_cnt = cnt.size(); //set妙用,相同的元素只能成功插入一次 //開始了,雙指標,此為同向移動指標,left為慢指標(左指標),i為快指標(右指標) int left = 0; int sum = 0; //sum統計兩指標內不同元素的個數 int min1 = n; //min1用來維護最小值 for (int i = 0; i < n; i++) //前進右指標 { if (each_cnt[a[i]] == 0) sum++; each_cnt[a[i]]++; if (sum == all_cnt) min1 = min(min1, i - left + 1); while (sum == all_cnt && left < i) //壓縮左指標 { if (each_cnt[a[left]] > 1) //最左邊的元素在區間內出現過不止一次,可以縮排 { each_cnt[a[left]]--; left++; } else //若只出現過一次則不能再縮進了 break; min1 = min(min1, i - left + 1);//維護最小值 } } printf("%d", min1); return 0; }

簡單雙指標題

1.不要npy!

牛客測試連結

題目描述
現在有一個長度為m的只包含小寫字母‘a’-‘z’的字串x,求字串中不同時含有n,p,y三個字母的最長字串的長度是多少?。(對於字串”abc”來說,”c”,”ab”都是原串的子串,但”ac”不是原串子串)

樣例
輸入:
“abcdefghijklmn”
輸出:
14
說明:
因為所有子串都不同時含有n,p,y,所以最長子串的長度即為字串x的長度14。

輸入:
“ynp”
輸出:
2
說明:
長度為2的字串”yn”,”np”都符合題意,不存在長度>=3的符合條件的子串。

輸入:
“ypknnbpiyc”
輸出:
7
說明:
“pknnbpi”為其符合條件的最長子串,長度為7。
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:
與入門題一樣,只不過是求max
class Solution {
public:
    int cnt[300]={0};
    bool check()//判斷n,p,y是否同時存在
    {
        return cnt['n'-'a']&&cnt['p'-'a']&&cnt['y'-'a'];
    }
    int Maximumlength(string x){
        int len=x.size();
        int left=0;//左指標
        int max1=0;//維護最大值
        for(int i=0;i<len;i++)
        {
            cnt[x[i]-'a']++;
            while(check())
            {
                cnt[x[left]-'a']--;
                left++;
            }
            max1=max(max1,i-left+1);
        }
        return max1;
    }
};
2.尋找所有相等和

zzuli2744測試連結
題目描述:
有一個數num
將所有正整數中區間和為num的區間打出來

輸入描述:
一個整數n
10 <= n <= 200000000

輸出描述:
每一行都輸出滿足和為n的區間

樣例:
輸入:
10000
輸出:
18 142
297 328
388 412
1998 2002
說明:
1998+1999+2000+2001+2002 = 10000
所以1998到2002為10000的一個解
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:
1.等差數列求和
2.利用與查詢的數n的關係判斷該向右移動右指標還是左指標
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstdlib>
#include <cstring>
#include <map>
#include <set>
using namespace std;
typedef long long ll;
int main()
{
    ll num;
    cin >> num;
    int left = 1, right = 2;//左右指標
    while(left<right)
    {
        ll ans = (left + right) * (right - left + 1) / 2;//兩指標之間的和
        if(ans==num)
            cout << left << " " << right << endl, right++;//十分重要,不加right++了話就會在碰到第一個成立的區間時死迴圈
        else if(ans<num)
            right++;//小了就推進右指標來擴大ans
        else
            left++;//大了就縮排左指標來減小ans
    }
    return 0;
}
3.尋找近似和

POJ2566測試連結

題目描述
在一陣列內
尋找與某個數num最接近的區間和的子區間

輸入描述:
多例項,輸入0 0截止
第一行輸入n,k,n表示陣列長度,k表示要查詢的數的個數
0<=n<=1000000
第二行輸入陣列,含n個整數
第三行輸入k個整數num
0<=num<=1000000000

輸出描述:
每一行輸出對應num查詢到的區間和、區間左邊界、區間右邊界。

樣例
輸入
5 1
-10 -5 0 5 10
3
10 2
-9 8 -7 6 -5 4 -3 2 -1 0
5 11
15 2
-1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
15 100
0 0
輸出
5 4 4
5 2 8
9 1 1
15 1 15
15 1 15
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:
//看見區間和就想到轉化為字首和計算
1.記錄字首和
2.排序字首和
3.利用與查詢的數num的關係判斷該向右移動右指標還是左指標
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstdlib>
#include <cstring>
#include <map>
#include <set>
using namespace std;
const int INF = 0x3f3f3f3f;
pair<int, int> sum[100010];//涉及子區間和的問題想字首和
int main()
{
    int n, k, num;
    while(scanf("%d%d",&n,&k),n+k)
    {
        sum[0].first = sum[0].second = 0;//first記錄字首和,second記錄編號
        for (int i = 1; i <= n;i++)
        {
            cin >> num;
            sum[i].first = sum[i - 1].first + num;
            sum[i].second = i;
        }
        sort(sum, sum + n + 1);//升序排列方便尋找
        while(k--)
        {
            cin >> num;
            //開始雙指標
            int left = 0, right = 1;//left為左指標,right為右指標
            int son_ans, son_left, son_right;
            int min1 = INF;//維護最小值
            while(right<=n&&min1)
            {
                int dir = sum[right].first - sum[left].first;//兩位置字首和差為其內部區間和
                if((int)abs(dir-num)<min1)
                {
                    min1 = (int)abs(dir - num);
                    son_ans = dir;
                    son_left = sum[left].second;
                    son_right = sum[right].second;
                }
                if(dir>num)//區間和大於num便把左指標右移動以減小區間和
                    left++;
                else//區間和小於num便把右指標右移動以增大區間和
                    right++;
                if(left==right)
                    right++;
            }
            if(son_left>son_right)//排序後遺症,容易顛倒位置
                swap(son_right, son_left);
            printf("%d %d %d\n", son_ans, son_left + 1, son_right);
        }
    }
    return 0;
}

思維雙指標題

1.分配休息日(非常規雙指標)

CodeForces1041C測試連結

題目描述:
Monocarp想休息,但他每次只能休息一小時,他的老闆不讓他連著休息,並且給他安排了兩次休息不能小於的間隔時間d,但Monocarp向老闆申請了n次休息的機會,每天工作m個小時,請問他如何安排每天的休息時刻(也就是如何把每個休息時刻安排在合理的日子),才能在最少的天數內用完這n次休息時間

輸入描述:
第一行n,m,d
1≤n≤2⋅105,n≤m≤109,1≤d≤m
第二行輸入n個數,代表他想休息的時刻

輸出描述:
第一行輸出用完這n次休息時刻的最少天數
第二行輸出n個數,為對應輸入中每個時刻應安排在第幾天

樣例:
輸入:
4 5 3
3 5 1 2
輸出:
3
3 1 1 2
說明:
第一天:1 5
第二天:2
第三天:3

輸入:
10 10 1
10 5 7 4 6 3 2 1 9 8
輸出:
2
2 1 1 2 2 1 2 1 1 2
說明:
第一天:1 3 5 7 9
第二天:2 4 6 8 10
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:
//學會雙指標要靈活應用,思考如何利用雙指標來卡區間
//此時我們要卡的區間便是不能被賦值指標賦值的空區間,
//增加其內部天數,
//所以此雙指標用來雙向賦值
1.貪心。升序排列出時刻大小,便於賦值指標移動賦值“第幾天”
{
    2.主指標確定某個時刻應放到第幾天
    3.賦值指標後移對可以匹配上主指標那一天的時刻賦值
}迴圈
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstdlib>
#include <cstring>
#include <map>
#include <set>
using namespace std;
typedef long long ll;
struct node
{
    int num;//表休息的時刻
    int day;//表該放在第幾天
    int ii;//表原陣列的順序i
} no[200005];
bool cmp1(node a, node b)//將休息的時刻從小到大
{
    return a.num < b.num;
}
bool cmp2(node a, node b)//將陣列恢復為輸入順序
{
    return a.ii < b.ii;
}
int main()
{
    int n, m, d;
    cin >> n >> m >> d;
    for (int i = 1; i <= n; i++)
        cin >> no[i].num, no[i].ii = i, no[i].day = 0;
    sort(no + 1, no + n + 1, cmp1);
    int cnt = 0;//可用來利用的天數
    for (int i = 1, j = 1; i <= n; i++)//注意:此時i為主指標,j為賦值指標(用來判斷將第j個休息時刻放在第幾天)
    {
        if (!no[i].day)
            no[i].day = ++cnt;
        while (j <= n && (no[j].day || no[j].num - no[i].num <= d))
            j++;//j移動到可以再次被放在第no[i].day天為止
        no[j].day = no[i].day;
    }
    sort(no + 1, no + n + 1, cmp2);
    cout << cnt << endl;
    for (int i = 1; i <= n; i++)
        cout << no[i].day << " ";
    return 0;
}