【C++算法竞赛】尺取法(双指针)

我是黑洞小白,欢迎来到算法竞赛系列!(先赞后看,互三必回!)

引言

在很多题目中,会有“请找出一个区间,满足…”这样的描述。但对于寻找一个区间,很多人第一反应就是:枚举左右端点。但这样的复杂度是O(n^2)的,太慢。于是,我们需要一个更快的、更适用的算法。它,就是——

尺取法

概念介绍

尺取法(又称双指针,Two Pointers),是一个常用的优化技巧,时间复杂度为O(n^2),用来解决序列区间问题。尺取法的前提条件是:区间是单调的,和二分法相同,所以很多题目用尺取和二分都行。

代码实现

问题和序列的区间有关,用 i, j 分别表示左右端点的下标(这就是所谓的指针)

暴力枚举

for (int i = 0; i < n; i++) { // 左端点
    for (int j = i + 1; j < n; j++) { // 右端点
        // 代码
    }
}

尺取法

显然,我们需要优化上面的代码。怎么优化?就是说,要去掉不必要的枚举,首先要明确一件事:序列在某一方面是有序的。既然是有序的,大的序列符合了,小的序列就不用再枚举了,直接加上大序列所包含的小序列数量。(先结合例题理解效果更好)

for (int i = 1, j = 0; i <= n; i++) {
    while (/*题目条件*/ && j < n) {
        j++; // 枚举j
    }
    if (/*题目条件*/)
        ans += (n - j + 1); // 改变答案

    //i++带来的变化"
}

算法实战

例题1:数列的部分和

题目描述

题目分析

首先需要枚举 i(左端点),它是单调的,从 1 到 n 。再看 j(右端点),从 i+1 到 n ,并对 a[j] 进行累加(一定是不断变大的,这点很重要),一旦 sum >= k 说明在 j 之后的所有右端点都是满足条件的(j, j+1, j+2 ...... n),在 ans 中加上这些数量,最后 i++ 。为什么 j 不用变?因为 i++ 后当前的总和一定比之前小,保证不会超过 k。(看不懂就多看几遍)

总之,在尺取中,单调性是非常重要的

题解
#include <bits/stdc++.h>
using namespace std;

int a[100010];
int main() {
    int n;
    long long k;
    cin >> n >> k;
    long long sum = 0, ans = 0;
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1, j = 0; i <= n; i++) {
        while (sum < k && j < n) {
            j++;
            sum += a[j];
        }
        if (sum >= k)
            ans += (n - j + 1);
        sum -= a[i];
    }
    cout << ans;
    return 0;
}

例题2:找相同数对

题目描述

题目分析

如果用尺取法做,那么首先要找到题目中的“单调”。将数组排序,然后就会发现每个数 A ,对应的数 B 一定是一段连续的区间。因为排序之后序列的有序性,我们枚举每个数,他们的左端点和右端点都是单调不降的。啧,单调不就出来了吗?

题解
#include <bits/stdc++.h>
using namespace std;

const int N = 2e5 + 10;
int n, c;
int a[N];

int main () {
	cin >> n >> c;
	for(int i = 1; i <= n; i++) cin >> a[i];
	sort(a+1, a+1+n);
	int l = 1, r1 = 1, r2 = 1;
	long long ans = 0;
	for(l = 1; l <= n; l++) {
		while(r1 <= n && a[r1] - a[l] <= c) r1++;
		while(r2 <= n && a[r2] - a[l] < c) r2++;
		if(a[r2] - a[l] == c && a[r1-1] - a[l] == c && r1 - 1 >= 1) 	
			ans += r1 - r2;
	}
	cout << ans;
	return 0;
}
  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值