引言
什么是双指针?
\quad \quad 严格的来说,双指针只能说是是算法中的一种技巧。双指针指的是在遍历对象的过程中,不是普通的使用单个指针进行访问,而是使用两个相同方向(快慢指针)或者相反方向(对撞指针)的指针进行扫描,从而达到相应的目的。
\quad \quad 双指针算法在一些数组题中很常用,双指针算法有两种形式,一种被称为对撞指针,两个指针从两端向中间靠拢;另一种是快慢指针,两个指针向统一方向运动,滑动窗口方法就是一种常用的快慢指针方法。
- 快慢指针:主要是成环问题
- 对撞指针:数组和字符串问题
- 滑动窗口:主要是子串问题
双指针问题套路
\quad \quad 通俗的说,就是在数组遍历中,我们使用两个指针进行操作。所以双指针问题基本有以下几个细节:
1、双指针的初始位置。
2、双指针的移动方法。
3、遍历的结束条件。
1、对撞指针
见解
\quad \quad 对撞指针是双指针的一种。对撞指针是指在有序数组中,将指向最左侧的索引定义为左指针(left),最右侧的定义为右指针(right),然后从两头向中间进行数组遍历。对撞数组适用于有序数组,也就是说当你遇到题目给定有序数组时,应该第一时间想到用对撞指针解题。
可解决问题类型
\quad \quad 对撞指针算法,可以用来解决有序数组中的数字之和,反转数组等之类的具有可以两边开工的一些问题。
求解的步骤与模板
\quad \quad 我们假设数组名字为 nums,数组长度为 n,数组首元素对应的位置为 0。
代码细节
(1)指针初始位置
-
左指针(left)一般指向数组的第一个元素。即 left = 0。
-
右指针(right)一般指向数组的最后一个元素。即 right = len-1。
(2)指针移动方法
-
左指针(left)向右边👉移动,一般每次移动一个位置,即 left++。
-
右指针(right)向左边👈移动,一般每次移动一个位置,即 right–。
(3)结束条件
- 左指针(left)位置和右指针(right)位置逆序即right < left。
2、快慢指针
见解
\quad \quad 快慢指针是两个指针从同一侧开始遍历数组,将这两个指针分别定义为快指针(fast)和慢指针(slow),两个指针以不同的策略移动,直到两个指针的值相等(或其他特殊条件)为止,如快指针(fast)每次增长两个,慢指针(slow)每次增长一个。
- 这种算法的两个指针的在数组上(或是链表上,序列上)的移动速度不一样。
- 通过控制指针不同的移动速度(比如在环形链表上),这种算法证明了他们肯定会相遇的。快的一个指针肯定会追上慢的一个。
可解决问题类型
- 移除元素,删除元素之类的。
- 判定链表中是否含有环
- 已知链表中含有环,返回这个环的起始位置
- 寻找链表的中点
- 寻找链表的倒数第 k 个元素
求解的步骤与模板
(1)指针初始位置
-
慢指针(slow)一般指向数组的第一个元素。即 slow = 0。
-
快指针(fast)一般指向数组的第一个元素。即 fast = 1。
(2)指针移动方法
-
慢指针(slow)向右边👉移动,一般每次移动一个位置,即 slow++。(移动步长根据具体题目具体分析)
-
快指针(fast)向右边👉移动,一般每次移动两个个位置,即 fast += 2。
(3)结束条件
- 慢指针(slow)位置和快指针(fast)位置重合;或快指针(fast)达到数组的最后一个元素。
3、滑动窗口
见解
\quad \quad 滑动窗口算法的本质是双指针法中的快慢指针法,滑动窗口算法是双指针法中的快慢指针法更为形象的一种表达方式。它可以将嵌套的循环问题,更高效的解决。是一种优化算法。
滑动窗口
是数组/字符串/链表问题(输入是一些线性结构)中常用的抽象概念。窗口
通常是在数组/字符串中由开始和结束索引定义的一系列元素的集合,即[i, j]
(左闭,右闭)。- 滑动窗口是可以将两个边界向某一方向“滑动”的窗口。
例如,我们将 [i, j]向右滑动 1个元素,则它将变为 [i+1, j+1]。
基本思想与原理
\quad \quad 滑动窗口算法,可以将双层嵌套的循环问题,转换为单层遍历的循环问题。使用两个指针一左一右构成一个窗口,就可以将二维循环的问题转化成一维循环一次遍历,相当于通过旧有的计算结果对搜索空间进行剪枝,使时间复杂度从 O ( n 2 ) O(n^2) O(n2)降低至 O ( n ) O(n) O(n)。
可解决问题类型
\quad \quad 滑动窗口算法可以用以解决数组、字符串的子元素、字串问题。比如可以用来解决一些查找满足一定条件的连续区间的性质(长度、区间和等)的问题。往往类似于“请找到满足xx的最x的区间(子串、子数组等)的xx”这类问题都可以使用该方法进行解决。
求解的步骤与模板
\quad \quad 滑动窗口算法的解题步骤是这样(以在字符串S中,找最小的子元素使之包含字符串T为例):
(1)指针初始位置即初始化窗口
- 初始化
left =0, right = -1
,把索引闭区间 [left, right] 称为一个「窗口」。
(2)指针移动方法——寻找可行解并优化可行解:
- 我们先不断地增加 right 指针扩张窗口 [left, right],直到窗口中的字符串符合要求(包含了 T 中的所有字符),找到可行解。
- 此时,我们停止增加 right,转而不断增加 left 指针缩小窗口 [left, right],直到窗口中的字符串不再符合要求(不包含 T 中的所有字符了)。同时,每次增加 left,我们都要更新一轮结果。
(3)滑动窗口,直至一次遍历结束:
- 重复第 2 3 步,直到 right 到达字符串 S 的尽头。
这个思路其实也不难理解,第 2 步中的第一扩张窗口相当于在寻找一个「可行解」,找到了就不再扩张;然后第二收缩窗口在优化这个「可行解」,直到条件被破坏,最终找到最优解。左右指针轮流前进,窗口大小增增减减,窗口不断向右滑动。
也就是说主要有以下三步:
- 扩张窗口:为了找到一个可行解,
- 收缩窗口:在长度上优化该可行解,直到条件被破坏
- 寻找下一个可行解,然后再优化到不能优化……
以上思路是比较形象的滑动窗口问题的解题步骤,这类题通常要寻找比较具体的窗口。因为滑动窗口算法是双指针法中的快慢指针法更为形象的一种表达方式,因此有的双指针法中的快慢指针法问题所求解比较抽象,没有具体的窗口,这就要求我们结合实际题目做出分析。