双指针

双指针

1. 算法分析

通过使得两个指针都不断向右移,将 O ( n 2 ) O(n^2) O(n2)的算法优化到 O ( n ) O(n) O(n)

2. 模板

2.1 维护窗口双指针

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

2.2 区间分割双指针

for (int i = 0, j = 0; i < s.size(); i = j) {
    j = i;
    while(j < s.size() && s[i] == s[j]) j++;  // 维护的区间为[i, j)
    v[cnt] = s[i] - '0';   // 维护的区间的开头为s[i] - '0'
    sz[cnt] = j - i;  // 维护的区间的长度为:j - i
    cnt++;  // 区间数目+1
}

3. 典型例题

3.1 维护窗口双指针

acwing799.最长连续不重复子序列
题意: 给定一个长度为n的整数序列,请找出最长的不包含重复数字的连续子序列,输出它的长度。1≤n≤100000
题解: 双指针维护一个区间,区间内每个数字都只出现过一次
代码:

#include <bits/stdc++.h>

using namespace std;

int const N = 1e6 + 10;
int a[N];

int main() {
    int n;
    cin >> n;
    int res = 0;
    unordered_map<int, int> st;
    for (int i = 0; i < n; ++i) scanf("%d", &a[i]);
    for (int i = 0, j = 0; i < n; ++i) {// 用双指针维护一个区间,区间内的数字只出现一次
        st[a[i]]++;
        while (j < i && st[a[i]] > 1) { // 当发现区间内的数字出现两次,移动j指针
            st[a[j]]--;
            j++;
        }
        res = max (res, i - j + 1);  // 更新区间内的最多数据数目
    }
    cout << res << endl;
    return 0;
}

acwing1238. 日志统计
题意: 小明维护着一个程序员论坛。现在他收集了一份”点赞”日志,日志共有 N 行。其中每一行的格式是:

ts id 

表示在 ts 时刻编号 id 的帖子收到一个”赞”。现在小明想统计有哪些帖子曾经是”热帖”。如果一个帖子曾在任意一个长度为 D 的时间段内收到不少于 K 个赞,小明就认为这个帖子曾是”热帖”。具体来说,如果存在某个时刻 T 满足该帖在 [T,T+D) 这段时间内(注意是左闭右开区间)收到不少于 K 个赞,该帖就曾是”热帖”。给定日志,请你帮助小明统计出所有曾是”热帖”的帖子编号。 1 ≤ K ≤ N ≤ 1 0 5 , 0 ≤ t s , i d ≤ 1 0 5 , 1 ≤ D ≤ 10000 1≤K≤N≤10^5,0≤ts,id≤10^5,1≤D≤10000 1KN105,0ts,id105,1D10000
题解: 首先按照时间从小到大排序,然后双指针维护一个区间,cnt记录当前区间里面每个id的出现次数,如果次数大于等于k,那么该id为热帖。
代码:

#include <bits/stdc++.h>

using namespace std;

#define x first
#define y second

int const N = 1e5 + 10;
typedef pair<int, int> PII;
PII logs[N];
int n, d, k, cnt[N], st[N];

int main() {
    cin >> n >> d >> k;
    for (int i = 0; i < n; ++i) {
        cin >> logs[i].x >> logs[i].y;
    }
    sort(logs,  logs + n);
    for (int i = 0, j = 0; i < n; ++i) {
        int id = logs[i].y;
        int ts = logs[i].x;
        cnt[id]++;  // 当前id次数加一
        while(j < i && ts - logs[j].x >= d) {  // 确保当前区间长度为d
            cnt[logs[j].y]--;
            j++;
        }
        if (cnt[id] >= k) st[id] = 1;  // 如果符合条件
    }
    for (int i = 0; i < N; ++i) if (st[i]) cout << i << endl;
    return 0;
}

3.3 区间分割双指针

Codeforces Round #681 B. Saving the City

题意: 给一段序列,1为有炸弹,0为无炸弹,要引爆所有炸弹,同时一个炸弹可以触发前后两个的炸弹。引爆一个炸弹的钱是a,放一个炸弹的钱是b。问引爆所有炸弹的最少花费。字符串长度为 1 0 5 10^5 105 1 < = a , b < = 1000 1<=a,b<=1000 1<=a,b<=1000

题解: 如果想要把所有的炸弹引爆,那么要不然不按照炸弹。如果要安装炸掉,必然是先装0少的地方。每次填满一段0,就会使得1的连续数目段减一。所有只需要把0的段长度截出来,然后按照从小到大排序,不断往里面填炸掉。因此答案的为: m i n { a ∗ c n t 1 , a ∗ ( c n t 1 − 1 ) + c n t 0 [ 1 ] ∗ b , a ∗ ( c n t 1 − 2 ) + ( c n t 0 [ 1 ] + c n t 0 [ 2 ] ) ∗ b , . . . } min\{a* cnt_1, a*(cnt_1 - 1) + cnt_0[1] * b, a*(cnt_1-2)+(cnt_0[1]+cnt_0[2])*b,...\} min{acnt1,a(cnt11)+cnt0[1]b,a(cnt12)+(cnt0[1]+cnt0[2])b,...}。需要不断抠出来1的段和0的段。

代码:

#include <bits/stdc++.h>

using namespace std;

int const N = 2e5 + 10;
typedef long long LL;
typedef pair<int, int> PII;

int n, m, T;
int v[N], sz[N];

int main() {
    cin >> T;
    while(T--) {
        memset(v, 0, sizeof v);
        memset(sz, 0, sizeof sz);
        cin >> n >> m;
        string s;
        cin >> s;
        int cnt = 0;
        for (int i = 0, j = 0; i < s.size(); i = j) {
            j = i;
            while(j < s.size() && s[i] == s[j]) j++;
            v[cnt] = s[i] - '0';
            sz[cnt] = j - i;
            cnt++;
        }
        vector<int> vec;
        int cnt_one = 0;
        for (int i = 0; i < cnt; ++i) 
            if (v[i] == 0) {
                if (i && i < cnt - 1) vec.push_back(sz[i]);
            }
            else cnt_one++;
        sort(vec.begin(), vec.end());
        LL res = (LL)n * cnt_one;
        LL sum = 0;
        for (int i = 0; i < vec.size(); ++i) {
            sum += vec[i];
            res = min(res, sum * m + (--cnt_one) * n);
        }
        cout << res << endl;
    }
    return 0;
}

Codeforces Global Round 11 B. Chess Cheater

题意: 给定一个长度为 2 ∗ 1 0 5 2*10^5 2105的字符串,字符串仅由’L’和’W’组成。如果当前位置是W,可以加一分,如果当前位置的前一个位置还是W,那么可以再加一分。现在给定m,表明最多可以把m个L变成W。问最多得分是多少。

题解: 总的得分可以等于 2 * w的数目 + 2 * k - L段没有填满的个数 - 1。因此,只需要把L段全部拿出来,尽可能把L段改成W,所以徐娅排序,然后从小到大取L段填。同时,首尾如果存在L,那么不需要取出来,因为如果首部存在,那么最后如果可以改的话,必然往后并,如果尾部存在,那么必然往前并,因此这两种情况,只需要最后多减一即可。

代码:

#include <bits/stdc++.h>

using namespace std;

int const N = 2e5 + 10;
typedef long long LL;
typedef pair<int, int> PII;

int n, m, T;
int v[N], sz[N];

int main() {
    cin >> T;
    while(T--) {
        cin >> n >> m;
        memset(v, 0, sizeof v);
        memset(sz, 0, sizeof sz);
        string s;
        cin >> s;
        int cnt = 0;
        LL res = 0;
        for (int i = 0, j; i < s.size(); i = j) {
            j = i;
            while(j < s.size() && s[i] == s[j]) j++;
            if (s[i] == 'W') v[cnt] = 0;  
            else v[cnt] = 1;
            v[cnt] = (s[i] == 'W'? 0: 1);// w--0, l--1
            sz[cnt] = j - i;    
            if (!v[cnt]) res += 2 * sz[cnt];
            cnt++;
        }
        res = min(res + 2 * m, (LL)s.size() * 2);
        vector<int> vec;
        for (int i = 0; i < cnt; ++i) if (v[i] == 1) vec.push_back(sz[i]);
        if (s.back() == 'L' && !vec.empty()) vec.pop_back();  // 删掉首部的L
        if (s[0] == 'L' && !vec.empty()) vec.erase(vec.begin());  // 删掉尾部的L
        sort(vec.begin(), vec.end());
        reverse(vec.begin(), vec.end());
        while(!vec.empty() && m - vec.back() >= 0) {
            m -= vec.back();
            vec.pop_back();
        }
        res = max((LL)0, res - (LL)vec.size() - 1);
        cout << res << endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值