基础算法——双指针算法

1. 核心思想
通过某种线性关系将原先O(n2)的朴素算法优化为O(n)的快速算法
2. 一般应用思路
(1)先写一个暴力的朴素O(n2)的算法
(2)找到两个指针之间的单调关系
(3)如果有单调关系,可以将枚举从O(n2)优化到O(n)
3. 模板

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

//具体问题的逻辑
}
常见问题的分类:
(1)对于一个序列,用两个指针维护一段区间,如:快速排序中用两个指针调整区间
(2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作

例题1:单词分割
把一个句子中用空格隔开的单词每个一行输出

#include<iostream>
#include<string.h>

using namespace std;

int main()
{
	char str[10010];

	gets(str);

	int n = strlen(str);	//字符串总长度	

	for(int i = 0; i < n; i++)
	{
		int j = i;	//每次j指针从i所在下标开始
		while (j<n && str[j] != ' ')
		{
			cout << str[j]; 
			j++;
		}
		cout << endl;
		i = j;
	}
	return 0;
}				

例2:最长连续不重复子序列
给定一个长度为n的整数序列,请找出最长的不包含重复数字的连续区间,输出它的长度。

输入格式
第一行包含整数n。
第二行包含n个整数(均在0~100000范围内),表示整数序列。
输出格式
共一行,包含一个整数,表示最长的不包含重复数字的连续子序列的长度。
数据范围
1≤n≤100000
输入样例:
5
1 2 2 3 5
输出样例:
3

一般朴素做法:
用一个两层循环
for(int i = 0; i < n; i++)
	for(int j = i; j < n; j++)
i在前,j在后维护的一段区间判重
这样j每次都要退回,时间复杂度为O(n^2)	
双指针做法:
让j在前, i在后维护一段区间
相当于一个队列,外层循环控制队尾,内层循环控制队头
#include<iostream>

using namespace std;

const int N = 100010;

int n;
int a[N], s[N];

int main()
{	
	cin >> n;
	for(int i = 0; i < n; i++) cin >> a[i];

	int res = 0; 	//序列长度
	for(int i = 0, j = 0; i < n; i++)
	{
		s[a[i]] ++;	//两个指针维护的区间内每个元素出现的次数
		
		//如果区间内有重复数字,把前面一个指针j向后移动
		while(s[a[i]] > 1)	
		{
			s[a[j]] --;
			j++;
		}	
			
		res = max(res, i - j + 1);	//计算最大序列长度
	}

	cout << res << endl;

	return 0;
}
	
	

例3:日志统计
小明维护着一个程序员论坛。现在他收集了一份”点赞”日志,日志共有 N 行。

其中每一行的格式是:

ts id

表示在 ts 时刻编号 id 的帖子收到一个”赞”。

现在小明想统计有哪些帖子曾经是”热帖”。

如果一个帖子曾在任意一个长度为 D 的时间段内收到不少于 K 个赞,小明就认为这个帖子曾是”热帖”。

具体来说,如果存在某个时刻 T 满足该帖在 [T,T+D) 这段时间内(注意是左闭右开区间)收到不少于 K 个赞,该帖就曾是”热帖”。

给定日志,请你帮助小明统计出所有曾是”热帖”的帖子编号。

输入格式
第一行包含三个整数 N,D,K。

以下 N 行每行一条日志,包含两个整数 ts 和 id。

输出格式
按从小到大的顺序输出热帖 id。

每个 id 占一行。

数据范围
1≤K≤N≤105,
0≤ts,id≤105,
1≤D≤10000
输入样例:
7 10 2
0 1
0 10
10 10
10 1
9 1
100 3
100 3
输出样例:
1
3

解题思路:
利用pair存储每条记录,排序后用双指针模拟队列,每次入队一个记录,并判断当前队列长度,如果队列长度大于限制区间长度,就从队头出队一个元素。入队出队的同时记录当前队列中元素出现的次数,如果大于k就输出

#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>

using namespace std;

const int N = 100010;

typedef pair<int, int> PII;

int n, d, k;
int cnt[N];
PII logs[N];
bool st[N];

int main()
{
    cin >> n >> d >> k;
    
    for(int i = 0; i < n; i++)
        cin >> logs[i].first >> logs[i].second;
    
    sort(logs, logs+n);
    
    memset(st, false, sizeof st);
    
    for(int i = 0, j = 0; i < n; i++)
    {
        int id = logs[i].second;
        cnt[id]++;
        
        while(logs[i].first - logs[j].first >= d) 
        {
            cnt[logs[j].second]--;
            j++;
        }
        
        if(cnt[id] >= k) st[id] = true;
    }
    
    for(int i = 0; i < N; i++)
        if(st[i]) cout << i << endl;
    
    return 0;
}

例4:完全二叉树的权值
给定一棵包含 N 个节点的完全二叉树,树上每个节点都有一个权值,按从上到下、从左到右的顺序依次是 A1,A2,⋅⋅⋅AN,如下图所示:

现在小明要把相同深度的节点的权值加在一起,他想知道哪个深度的节点权值之和最大?

如果有多个深度的权值和同为最大,请你输出其中最小的深度。

注:根的深度是 1。

输入格式
第一行包含一个整数 N。

第二行包含 N 个整数 A1,A2,⋅⋅⋅AN。

输出格式
输出一个整数代表答案。

数据范围
1≤N≤105,
−105≤Ai≤105
输入样例:
7
1 6 5 4 3 2 1
输出样例:
2

解题思路:
题目要求求出和值最大的层数。按照完全二叉树的标号顺序,可以找到每一层起始元素与元素个数,所以只要利用双指针算法,一个指针遍历起始元素,另一个指针遍历每一层各个元素累加,就可以求出每层的和值,维护最大值即可。

#include<iostream>
#include<algorithm>

using namespace std;

const int N = 100010;
typedef long long LL;

int a[N];
int n;

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
    
    LL maxv = -1e18;
    int depth = 0;
    
    for(int i = 1, d = 1; i <= n; d++, i *= 2)
    {
        LL sum = 0;
        for(int j = i; j - i + 1 <= 1 << d - 1 && j <= n; j++)
            sum += a[j];

        if(sum > maxv)
        {
            maxv = sum;
            depth = d;
        }
    }
    
    cout << depth << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值