双指针算法
文章目录
一、前言
今天,我们来重温算法题型之滑动窗口问题~在大题中算是一种优化的策略
二、基本介绍
滑动窗口/n指针/尺取法,本质都是一样的。
利用双/n指针遍历获取满足条件的区间的算法
三、题型
3.1 单序列
3.1.1 同向双指针
思路
枚举r,不断得到合法区间(符合条件,但不一定是答案)。记(l, r]为一个序列内以r为终点的合法区间,然后枚举l,随着l的增大,区间不断缩小,直到不合法为止(真正筛选,得到答案)。
总结:滑动窗口相当于在维护一个队列 。右指针的移动可以视作入队 ,左指针的移动可以视作出队。
和哈希表结合:
cpp
// 定长滑窗
// 入
cnt[nums[i]]++;
// 更新(根据题目条件来)
// 出
if(--cnt[out] == 0)
{
cnt.erase(out);
}
// 不定长滑窗
// 入
cnt[c]++;
// 更新 + 出(根据题目条件来)
while(cnt[c] > 2)
{
cnt[s[left]]--;
left++;
}
时间复杂度 : O ( n ) O(n) O(n)
用法 :求取有一定限制的区间个数 或最短的区间(与区间相关)
优点
- 不会去枚举到一定不满足条件的区间
- 不会去枚举符合条件但是一定不是答案的区间
不足:有些情况不可行(双指针的情况有很多种)
满足条件 :区间和大小满足区间长度单调变化,即:区间越长,区间和越小/大
模板:
cpp
int n=nums.size();
int l=0,r=0;
int sum=0;
while(r<n)
{
sum+=nums[r];
while(l<=r&&sum-nums[l]>=target)
{
sum-=nums[l];
l++;
}
if(sum>=target)
{
res=min(res,r-l+1);
}
r++;
}
-
初始化左右指针,答案和辅助变量
-
枚举r(移动右指针),寻找符合条件的,枚举r到超出条件时,就该枚举l了
-
枚举l来寻找答案
例题
leetcode
- LCR 008.长度最小的子数组(不定长:对于条件更注意,没有明确的出)
- 713.乘积小于 K 的子数组(子区间问题 :下标从0开始,短:子区间个数:
ans += r - l + 1;长:ans += l) - 2379.得到 K 个黑块的最少涂色次数(定长:入-更新-出)
3.1.2 相向双指针
思路
两个指针left = 0; right = n − 1,从数组的两端开始,向中间移动,这叫相向双指针 。上面的滑动窗口相当于同向双指针。
模板
cpp
int i = 0, j = s.size() - 1;
while (i < j) {
if (!isalnum(s[i])) {
i++;
} else if (!isalnum(s[j])) {
j--;
} else if (tolower(s[i]) == tolower(s[j])) {
i++;
j--;
} else {
return false;
}
}
return true;
终止条件一般为:
i < j
适用题型
- 有序(和大小相关)
- 回文(和首尾检验有关)
例题
- 125.验证回文串
3.2 双序列
遍历时兼顾两个序列,和单序列差异不大,甚至更简单
3.3 三指针
3.3.1 思路
一个指针枚举位置,i = 0,j = i + 1,k = arr.size() - 1
3.4 分组循环
3.4.1 核心思想
外层循环负责遍历组之前的准备工作(记录开始位置),和遍历组之后的统计工作(更新答案最大值)。
内层循环负责遍历组,找出这一组最远在哪结束。
3.4.2 优点
各个逻辑块分工明确,也不需要特判最后一组(易错点)。
3.4.3 适用场景
按照题目要求,数组会被分割成若干组,每一组的判断/处理逻辑是相同的。
3.4.4 思路
cpp
// 定义变量
// 一般而言,只需一个变量i充当指针作用即可
while(i < n)
{
// 第一个while负责寻找各组的起点
int start = i;
while(i < n && ...)
{
// 第二个while寻找各组的终点
}
ans = ...; // 更新答案
}
3.4.5 例题
2760.最长奇偶子数组
四、小结
本篇结合灵神的题单总结,很多大佬的总结以及自己的感悟而来。。。