算法思想:双指针

算法思想

双指针其实是利用数组(或者求解问题)的有序特性,在遍历的过程中使用两个相同方向或者相反方向的指针进行扫描,从而达到目的的一种算法。
(注:这里的指针并非专指c语言中的指针,表达的含义是下标、索引值或者是可进行迭代的对象等)

算法模板

我们常见的一般的二重循环如下:

for(int i = 0; i < n; i ++) {
    for(int j = 0; j < n; j ++) {
    }
}

而双指针算法可以将上面的二重循环优化成这样(模板不唯一):

for(int i = 0, j = 0; i < n; i ++) {
    while(j < i && check(i,j)) {
        j++;
    }
}

虽然看上去仍然是循环套循环,但是实际上是一个O(n)的时间复杂度。
为什么可以到达O(n)的时间复杂度呢?因为每一个指针在所有循环里面总共移动的次数是不超过n的,那么两个指针总共移动的次数不超过2n次。

算法应用

一般来讲常见的双指针算法分为两种:
(1) 对于一个序列,用两个指针维护一段区间,比如快排。
]{GHFUF[OD1Z%3)U}6D5R5D.png

例题1 AcWing 799.最长连续不重复子序列(属于第一类)

思路:最开始让i、j指针均指向数组的开始位置,再移动i指针,当i指针已经无法再右移时(a[i]所表示的值已经在前面序列中出现过,用s数组判重处理),可获得一段下标为[j,i]的连续不重复子序列。再将j指针后移(此时while循环起作用),边移动j指针一边在s表示的数组中剔除a[j]表示的值,j指针移动到i指针位置时,便可进行下一段子序列的扫描。
代码:

#include 
#include 
using namespace std;

const int N = 100010;
int n;
int a[N];
int s[N];   // 当前j到i的区间里,每一个数出现的次数
// 可以不开数组判重,但需要使用hash表

int main() {
    
    cin >> n;
    for(int i = 0; i < n; i ++) {
        scanf("%d", &a[i]);
    }
    
    int res = 0;
    for(int i = 0, j = 0; i < n; i ++) {
        s[a[i]] ++;
        while(s[a[i]] > 1) {    // 当a[i]值的个数大于1时
            s[a[j]] --;    // 剔除该数
            j ++;   // 右移j指针
        }
        res = max(res, i - j + 1);
    }
    
    cout << res << endl;
    
    return 0;
}

例题2 AcWing 1238.日志统计(属于第一类)

思路:由题意可知,我们通过维护时间段的区间来进行双指针的算法。开始时i和j指针均为时间段的开始位置,随着时间的推移(i指针后移),往cnt数组里面添加id出现的次数,一旦大于k次,便用st数组将其记录为热帖。
代码:

#include 
#include 
#include 
using namespace std;

typedef pair PII;    // 需要对二元组排序时使用,默认以first为第一个关键字排序

const int N = 1e5 + 10;
int n, d, k;
PII logs[N];
int cnt[N];
bool st[N];  // 记录每个帖子是否是热帖

int main() {
    
    cin >> n >> d >> k;
    for(int i = 0; i < n; i ++) {
        scanf("%d %d", &logs[i].first, &logs[i].second);
    }
    
    sort(logs, logs + n);
    
    for(int i = 0, j = 0; i < n; i ++) {
        cnt[logs[i].second] ++;
        while(logs[i].first - logs[j].first >= d) {    // id的时间段大于了题意的d
            cnt[logs[j].second] --;    // 剔除该id
            j ++;    // j指针后移
        }
        if(cnt[logs[i].second] >= k) {
            st[logs[i].second] = true;
        }
    }
    
    for(int i = 0; i <= 100000; i ++) {
        if(st[i]) {
            printf("%d\n", i);
        }
    }
    
    return 0;
}

(后续题目待补充)

算法总结

可以先想一个暴力O(n^2)的做法,然后找i与j之间的单调关系再进行双指针优化。

你可能感兴趣的:(算法,双指针)