总结
常⻅的双指针有两种形式,⼀种是对撞指针,⼀种是左右指针。
对撞指针:⼀般⽤于顺序结构中,也称左右指针。
对撞指针从两端向中间移动。⼀个指针从最左端开始,另⼀个从最右端开始,然后逐渐往中间逼
近。
对撞指针的终⽌条件⼀般是两个指针相遇或者错开(也可能在循环内部找到结果直接跳出循
环),也就是:
left == right
(两个指针指向同⼀个位置)
left > right
(两个指针错开)
快慢指针:⼜称为⻳兔赛跑算法,其基本思想就是使⽤两个移动速度不同的指针在数组或链表等序列结构上移动。
这种⽅法对于处理环形链表或数组⾮常有⽤。其实不单单是环形链表或者是数组,如果我们要研究的问题出现循环往复的情况时,均可考虑使⽤快慢指针的思想。
快慢指针的实现⽅式有很多种,最常⽤的⼀种就是:
在⼀次循环中,每次让慢的指针向后移动⼀位,⽽快的指针往后移动两位,实现⼀快⼀慢。
例题一
解法(快排的思想:数组划分区间 - 数组分两块):
算法思路:
在本题中,我们可以⽤⼀个
cur
指针来扫描整个数组,另⼀个
dest
指针⽤来记录⾮零数序列
的最后⼀个位置。根据cur 在扫描的过程中,遇到的不同情况,分类处理,实现数组的划分。
在
cur
遍历期间,使[0, dest] 的元素全部都是⾮零元素,
[dest + 1, cur - 1]
的元素全是零。
算法流程:
a.
初始化 cur = 0
(⽤来遍历数组),
dest = -1
(指向⾮零元素序列的最后⼀个位置。
因为刚开始我们不知道最后⼀个⾮零元素在什么位置,因此初始化为
-1
)
b.
cur 依次往后遍历每个元素,遍历到的元素会有下⾯两种情况:
i.
遇到的元素是 0
,
cur
直接++ 。因为我们的⽬标是让[dest + 1, cur - 1] 内的元素全都是零,因此当 cur遇到 0
的时候,直接
++,就可以让 0
在
cur - 1的位置上,从⽽在[dest + 1, cur - 1] 内;
ii.
遇到的元素不是 0, dest++
,并且交换
cur
位置和
dest
位置的元素,之后让cur++ ,扫描下⼀个元素。
![](https://img-blog.csdnimg.cn/direct/eedb8d72967b44938dea41c9c4ae91e4.png)
例题二
![](https://img-blog.csdnimg.cn/direct/fe164c4a72754e4a96b964c95978e65a.png)
解法(原地复写 - 双指针):
算法思路:
如果「从前向后」进⾏原地复写操作的话,由于
0
的出现会复写两次,导致没有复写的数「被覆
盖掉」。因此我们选择「从后往前」的复写策略。但是「从后向前」复写的时候,我们需要找到「最后⼀个复写的数」,因此我们的⼤体流程分两步:
i.
先找到最后⼀个复写的数;
ii.
然后从后向前进⾏复写操作。
算法流程:
a.
初始化两个指针 cur = 0
,
dest = 0
;
b.
找到最后⼀个复写的数:
i.
当cur < n的时候,⼀直执⾏下⾯循环:
判断
cur位置的元素:
如果是 0
的话,
dest
往后移动两位;
否则, dest
往后移动⼀位。
判断
dest
时候已经到结束位置,如果结束就终⽌循环;
如果没有结束, cur++
,继续判断。
c.
判断 dest
是否越界到
n
的位置:
i.
如果越界,执⾏下⾯三步:
1.
n - 1 位置的值修改成
0
;
2.
cur 向移动⼀步;
3.
dest 向前移动两步。
d.
从cur 位置开始往前遍历原数组,依次还原出复写后的结果数组:
i.
判断 cur
位置的值:
1.
如果是 0
:
dest
以及
dest - 1
位置修改成
0
,
dest -= 2
;
2.
如果⾮零: dest
位置修改成
0
,
dest -= 1
;
ii.
cur-- ,复写下⼀个位置。
![](https://img-blog.csdnimg.cn/direct/0af1f0b775d443d0aeda03670a0aa425.png)
例题三
![](https://img-blog.csdnimg.cn/direct/42f92602ceaf4737a7e939f1f322b35f.png)
解法(快慢指针):
算法思路:
根据上述的题⽬分析,我们可以知道,当重复执⾏
x
的时候,数据会陷⼊到⼀个「循环」之中。
⽽「快慢指针」有⼀个特性,就是在⼀个圆圈中,快指针总是会追上慢指针的,也就是说他们总会
相遇在⼀个位置上。如果相遇位置的值是
1
,那么这个数⼀定是快乐数;如果相遇位置不是
1 的话,那么就不是快乐数。
![](https://img-blog.csdnimg.cn/direct/87dccb1d1acc41cc855091f0e2b16852.png)
例题四
![](https://img-blog.csdnimg.cn/direct/71f9c493135a46129e443d23dbee2508.png)
解法(对撞指针):
算法思路:
设两个指针
left
,
right
分别指向容器的左右两个端点,此时容器的容积 :
v = (right - left) * min( height[right], height[left])
容器的左边界为
height[left]
,右边界为
height[right]
。
为了⽅便叙述,我们假设「左边边界」⼩于「右边边界」。
如果此时我们固定⼀个边界,改变另⼀个边界,⽔的容积会有如下变化形式:
容器的宽度⼀定变⼩。
由于左边界较⼩,决定了⽔的⾼度。如果改变左边界,新的⽔⾯⾼度不确定,但是⼀定不会超
过右边的柱⼦⾼度,因此容器的容积可能会增⼤。
如果改变右边界,⽆论右边界移动到哪⾥,新的⽔⾯的⾼度⼀定不会超过左边界,也就是不会
超过现在的⽔⾯⾼度,但是由于容器的宽度减⼩,因此容器的容积⼀定会变⼩的。
由此可⻅,左边界和其余边界的组合情况都可以舍去。所以我们可以
left++
跳过这个边界,继
续去判断下⼀个左右边界。
当我们不断重复上述过程,每次都可以舍去⼤量不必要的枚举过程,直到
left
与
right
相
遇。期间产⽣的所有的容积⾥⾯的最⼤值,就是最终答案。
![](https://img-blog.csdnimg.cn/direct/285ac73aafff46a1ae4bd12c52d2481b.png)
例题五
解法(排序 + 双指针):
算法思路:
先将数组排序。
我们可以固定⼀个「最⻓边」,然后在⽐这条边⼩的有序数组中找出⼀个⼆元组,使这个⼆元组之和⼤于这个最⻓边。由于数组是有序的,我们可以利⽤「对撞指针」来优化。
设最⻓边枚举到
i
位置,区间
[left, right]
是
i位置左边的区间(也就是⽐它⼩的区间):
如果
nums[left] + nums[right] > nums[i]
说明
[left, right - 1]
区间上的所有元素均可以与
nums[right]
构成⽐nums[i] ⼤的⼆元组 ,
满⾜条件的有 right - left
种 ,
此时 right
位置的元素的所有情况相当于全部考虑完毕,
right--
,进⼊下⼀轮判断 。
如果
nums[left] + nums[right] <= nums[i]
:
说明
left
位置的元素是不可能与
[left + 1, right]
位置上的元素构成满⾜条件的⼆元组
left
位置的元素可以舍去,
left++
进⼊下轮循环
![](https://img-blog.csdnimg.cn/direct/727456039c574112a6d7dcb57918138d.png)
例题六
解法(排序+双指针):
算法思路:
i.
先排序;
ii.
然后固定⼀个数 a
:
iii.
在这个数后⾯的区间内,使⽤「双指针算法」快速找到两个数之和等于 -a
即可。
但是要注意的是,这道题⾥⾯需要有「去重」操作~(可以放入set去重)
i.
找到⼀个结果之后, left
和
right 指针要「跳过重复」的元素;
ii.
当使⽤完⼀次双指针算法之后,固定的 a 也要「跳过重复」的元素。
![](https://img-blog.csdnimg.cn/direct/146b1349716f4eceb7da0a63e627f083.png)
例题七
解法(排序 + 双指针)
算法思路:
a.
依次固定⼀个数 a
;
b.
在这个数 a
的后⾯区间上,利⽤「三数之和」找到三个数,使这三个数的和等于
target
- a
即可。
![](https://img-blog.csdnimg.cn/direct/d63fa500928b466b8ff6ecd4987c19d9.png)