算法基础系列
概念
所谓双指针算法,就是指的是在遍历的过程中,不是普通的使用单个指针进行循环访问,而是使用两个相同方向或者相反方向的指针进行扫描,从而达到相应的目的
双指针的形式有很多,但核心思想是降低时间复杂度
例如:对于 i , j i,j i,j 的双重循环,时间复杂度是 O ( n 2 ) O(n^2) O(n2),而运用了双指针算法优化之后的时间复杂度是 O ( n ) O(n) O(n)
优化的本质是找 i i i 和 j j j 的变化规律——单调性
一般做法:
- 先想一个朴素(暴力)做法,时间复杂度是 O ( n 2 ) O(n^2) O(n2)
- 找规律,看看题目有什么规律
- 优化
模板代码
for(int i=0,j=0;i<n;i++)
{
while(j<i && check(i,j)) j++;
//每道题目的具体逻辑
}
最简单的模板运用:输入一个字符串,输出每个单词(以空格隔开)
建议画图理解
int n = strlen(str);
for(int i=0;i<n;i++)
{
int j=i;//第二个指针
while(j<n && str[j] != ' ') j++;
//这道问题的具体逻辑
for(int k=i;k<j;k++) cout<<str[k];
cout<<endl;
i=j;//i 等于了j 的位置,也就是空格位置,然后再 i ++,指向了下一个单词的首字母
}
练习题
最长连续不重复子序列 – 模板题
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, res;
int a[N];
int st[N];
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i++)
scanf("%d", &a[i]);
for (int i = 0, j = 0; i < n; i++)
{
st[a[i]]++; //向右扩展区间右端点
//当扩展完区间右端点之后,有可能这个元素q[i]会有重复,下面这个while循环就是用来去除重复
//去重只有一个办法,就是收缩区间左端点,同时收缩时要保证j是小于i的
while (j < i && st[a[i]] > 1)
st[a[j++]]--;//区间数量减一,之后区间断点右移
res = max(res, i - j + 1);
}
printf("%d", res);
return 0;
}
数组元素的目标和
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, m, k;
int a[N], b[N];
int main()
{
scanf("%d%d%d", &n, &m, &k);
for (int i = 0; i < n; i++)
{
scanf("%d", &a[i]);
}
for (int i = 0; i < m; i ++)
scanf("%d", &b[i]);
for (int i = 0, j = m-1; i < n; i ++)
{
while(j >=0 && a[i] + b[j] > k)
j--;
if(j >= 0 && a[i] + b[j] == k)
printf("%d %d", i, j);
}
return 0;
}
判断子序列
#include <algorithm>
#include <cstdio>
#include <iostream>
using namespace std;
// 新增的双指针模板题
// 基本思路就是遍历一遍“子序列” ,
// 看看其每一个元素另一个数组中是不是在都有各自的下标
// 且下标要呈递增趋势,这就有了单调性 可以用双指针
int main()
{
int n, m, i, j;
scanf("%d%d", &n, &m);
int a[n], b[m];
for (i = 0; i < n; i++)
scanf("%d", &a[i]);
for (j = 0; j < m; j++)
scanf("%d", &b[j]);
i = 0, j = 0;
//记得复原下ij
while (i < n && j < m)
{
if (a[i] == b[j])
i++;
j++;
//这个循环可理解为 对于a[i]
// b从j开始遍历 直到a[i] =b[j]了,让i+=1
//对于新的a[i],重复上行过程
//如此一来实质上就是以a[i]是否=b[j] 遍历了一遍b,注意是一遍
//期间若a[i] == b[j]了 更新a[i](即i++)继续遍历
}
if (i == n)
printf("Yes\n");
//从上面的循环出来了 若a[i]遍历完了(i == n)
//就说明a是子序列 反之就不是
else
printf("No");
return 0;
}
日志统计
传送门
按订单排序
#include <bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 1e5+10;
int n, d, k;
PII logs[N];//记录日志
int cnt[N];
bool st[N];// 记录是否是热帖
int main()
{
scanf("%d%d%d", &n, &d, &k);
for (int i = 0; i < n; i ++)
scanf("%d%d",&logs[i].x,&logs[i].y);
sort(logs, logs + n);
for (int i = 0,j = 0; i < n; i++)//枚举订单
{
int id = logs[i].y;
cnt[id] ++;
while(logs[i].x - logs[j].x >= d )
{
cnt[logs[j].y]--;
j++;
}
if(cnt[id] >= k)
st[id] = true;
}
for (int i = 0; i <= N; i ++)
if(st[i])
printf("%d\n", i);
return 0;
}
完全二叉树的权值
理解完全二叉树的定义
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int n;
int a[N];
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
/*
完全二叉树 每层的开头为 2^(n-1) 结尾则是 2^n - 1
计算每层的数值只需要两个positioner 分别指向开头和结尾
*/
LL maxv = -1e18;
int depth = 0;
for (int i = 1, d = 1; i <= n; i *= 2,d++)
{
LL s = 0;
for (int j = i; j < i + (1 << d - 1) && j <= n; j++)
s += a[j];
if (s > maxv)
{
maxv = s;
depth = d;
}
}
cout << depth << endl;
}