acwing算法笔记-双指针
双指针算法
本质:
双指针算法顾名思义就是要维护两个指针,这两个指针可能指向同一个序列当中的不同位置,也有可能指向不同的序列。但其本质就是利用单调性缩减枚举空间
枚举一个指针,找另一个合法的指针,这里check指的是维护合法性质(根据题目条件判定,简单来说while不合法移动指针到合法为止,这样就找到了合法的另一个指针)
两个指针的位置的排列组合有$n^2$种,相应的时间复杂度也是$O(n^2)$,但如果应用双指针算法,可以将时间复杂度降至$O(n)$:比如说两个指针不回退扫描同一个序列,每一个指针在上图所有的循环里面总共移动的次数是不超过n的,我们可以应用某些单调的性质实现这一点。快速排序,归并排序,KMP算法本质上都是双指针算法。
双指针算法的解题思路大概是这样:暴力枚举->分析两个指针之间的单调性(探索有没有可能一个指针随着另一个指针的变化而变化)->通过单调性将暴力解法的时间复杂度降低一维
注意事项:
- 如果双指针指向一个序列,那么while里的另一个指针的条件就是指针相遇之前
- 如果双指针指向两个序列,那么while里的另一个指针的条件就是不越界
- 分析的时候合法条件可以分离出一部分处理,例如移动这种条件
799. 最长连续不重复子序列
基本思路
双指针问题一般可以先从暴力做法开始思考,对于上述问题,可以暴力枚举子串的起点和终点
1
2
3
4for (int i =0, j =0; i < n; i ++)
for (int j = 0; i <= i; j ++)
if (check(j, i)) //合法 满足最长不重复子串性质
res = max(res, i - j + 1); //对于所有i的最长不重复子串 找到最长的分析i和j是否存在单调性
首先i是指向子串终点的指针,枚举i指针,我们去找合法的j指针(这里的合法也就是最长不重复子串):在每次迭代中,我们让i往后移一个位置,随着i往后移动,j只能随之一起向后移动,不可能往前移动(因为要维护最长不重复子串合法,可以反证法证明)
因此,我们发现了双指针存在单调性,可以优化枚举空间,优化后的伪代码如下:
1 |
|
这个算法当中,i和j两个指针它们只会加起来最多移动2n次,因此时间复杂度降到了O(n)
所以说双指针算法本质上就是要发现朴素算法中的一些性质,尤其是单调性,让我们由需要枚举n^2个状态变成需要枚举n个状态
注意:
- 哈希表维护的区间,如果value是次数,一般我们可以直接数组模拟哈希表更方便
- 如果用unordered_map模拟的话,如果维护次数用的是key,那我们统计key用count函数,变化key用erase函数;
如果维护次数用的是value,那我们统计用hash[k],变化value就是变化hash[k]
不管是用key还是用value,对于插入操作,insert函数和hash[k]变化效果一样 - 大数组要开在全局下,不能开在main等其他函数中,否则会爆栈
参考代码
1 |
|
Reference
800. 数组元素的目标和
基本思路
暴力枚举两个数时间复杂度$O(n^2)$所以想办法优化,这里发现了指针之间的单调性,所以可以用双指针优化,然后这里是反向双指针
题目很简单,这里就注意两点
- 因为是反向双指针,所以其实位置是两侧
- 我们双指针算法找到的是对于每一个i的合法方案,这里的合法不能写
a[i] + b[j] == x
,因为不是每一个i都会有这种方案存在,
但是起初的时候i指向0,所以一定满足是所有i里最小的,再加上一定有解,所以合法条件是a[i] + b[j] <= x
参考代码
1 |
|
Reference
2816. 判断子序列
基本思路
这题很明显可以用暴力解法求解,枚举a[]里的数,然后对于每一个数扫描一遍b[]看是否在里面,时间复杂度显然是$O(n^2)$,所以我们尝试优化。
这里发现指针之间的单调性,假设i指向a数组,j指向b数组,对于当前i我们找到了合法的j,如果i后移,因为要保证子序列按原有次序排序,所以j也只能往后移,发现单调性,可以用双指针算法优化。
注意这里合法的j实际上是有两个条件 1. 值相同 2. 并且j至少移动一位,代码实现就是a[i] = b[j] and j > 上一层的j, 在这里大于上一层的j不是很好拿到。
所以我们把j至少移动一位的条件分离了出来,这样更容易实现。
参考代码
1 |
|