文章目录
- 前言
- 什么是数组双指针
- 数组中删除元素专题
- 原地移除所有等值val的元素
- 快慢双指针
- 对撞双指针
- 对撞+覆盖
- 删除有序数组中的重复项
- 总结
前言
提示:世间从不缺少辉煌的花冠,缺少的是被花冠渲染的淡定。
什么是数组双指针
这是一种长期总结下来的思想,可以说现在我们是站在巨人的肩膀上创作。
简单来说,数组里面的双指针,并不真的是指针,这里也没有指针的概念。双指针的思想好用,也存在特定的场景,比如数组,字符串等问题上面。我们举个例子说明吧。
26. 删除有序数组中的重复项 - 力扣(LeetCode)
就拿这个题目来说吧,数组最简单的的想法是,找到一个重复的数组,整体向前移动,看示例1:[1, 1,2], 找到第二个 1 后面的元素整体就向前移动(覆盖),当然这样的效率太低了。对示例2来说需要前后移动好几次才可以,不推荐使用☠。我们采用双指针的想法就不一样了,我们画个图简单说明一下:
思路:
- 定义两个指针slow,fast。slow便是当前位置之前的元素都不是重复的,而fast则一直向后找,知道找到和slow位置不一样的
- 找到不一样的后,slow向后移动一步,将arr[fast]的值复制给arr[slow],之后fast再继续向后循州,知道循环结束。
- 循环结束后slow以及之前的元素就是单一的了。
当然这种是比较典型在双指针中的快慢指针了,除此之外还有其他的变题,对撞指针、背向指针等;
可以简单的这样去记双指针的三种经典模式:
- 两个指针一起向前走(相亲相爱一起走)
- 两头向中间走(冲破千难万险来爱你)
- 中间向两头走(缘分已尽,就此拜拜)
数组中删除元素专题
所谓算法,其实将一个问题改改条件多折腾,要掌握核心点
原地移除所有等值val的元素
27. 移除元素 - 力扣(LeetCode)
在删除的时候,从删除位置的所有元素都需要向前移动,所以这提的关键是如果有很多的val的元素的是侯需要如何避免反复的向前移动呢
这个题就可以采用双指针的思想,以下是三种方法,可以了解一下
快慢双指针
整体思路和上面所画的图是一样的,定义两个指针slow和fast,初始值都是0。
slow之前的位置都是有效的部分,fast表示当前要访问的元素。
这样遍历的时候,fast不断的向后移动:
- 如果nums[fast]的值不是val,就继续向前移动到nums[slow++]处
- 如果nums[fast]的值为val,则fast继续向前移动,slow先等待。
画图展示:
这样表示的话,前面一部分是有效的,后面一部分是无效的
代码如下展示❤:
/**
* 方法1:使用快慢型双指针
*
* @param nums
* @param val
* @return
*/
public static int removeElement(int[] nums, int val) {
// 定义快慢指针
int slow = 0;
for (int fast = 0; fast < nums.length; fast++) {
if (nums[fast] != val) {
nums[slow] = nums[fast];
slow++;
}
}
//最后剩余元素的数量
return slow;
}
对撞双指针
对撞双指针,有的地方说是交换移除,核心思想是从右边找到不是val的值顶替左边的val的值。
我们看下图详解:
上图就是整体的思路,当left == right 的时候,left 以及左边的就是所有元素了
代码展示:
/**
* 方法2:使用对撞型双指针
*
* @param nums
* @param val
* @return
*/
public static int removeElement2(int[] nums, int val) {
// 定义左右指针
int left = 0;
int right = nums.length - 1;
// left > right 退出循环
while (left <= right) { // 可以替换成 for(left = 0;left <= right
// 满足条件就就换数据 该条件要放在前面
if (nums[left] == val && nums[right] != val) {
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
// 左边放有效数据
if (nums[left] != val) {
left++;
}
// 右边放无效数据
if (nums[right] == val) {
right--;
}
}
return left;
当然这就是比较中规中矩的对撞双指针模式。这里注意顺序,条件顺序问题。
对撞+覆盖
结合上面的两种方法:当nums[left] == val 的时候,我们就直接将 nums[right]的位置上的元素直接覆盖nums[left],然后继续循环,否则才让left++当然这也属于双指针的思想,我们先画画图更好理解:
这时候写代码是不是很简单🥰
/**
* 方法三:优化对撞型双指针
*
* @param nums
* @param val
* @return
*/
public static int removeElement3(int[] nums, int val) {
// 定义左右指针
int left = 0;
int right = nums.length - 1;
while (left <= right) {
if (nums[left] == val) {
nums[left] = nums[right];
right--;
} else {
left++;
}
}
return right + 1;
}
对撞型双指针的过程与后面要学习的快速排序是一个思路,快速排序要比较多轮,而这里只执行了一轮。理解这里对于理解快速排序很重要。
注意:我们可以发现快慢型双指针留下的元素顺序与原始序列中的一直,当然对撞型就不一样了。
删除有序数组中的重复项
26. 删除有序数组中的重复项 - 力扣(LeetCode)
这个题使用双指针来说,方便很多,一个指针负责数组遍历,一个指针指向有效数组的最后一个位置。为了减少不必要的操作,我们这里做一些调整,令slow = 1,比较对象换做nums[slow -1] (这个操作真的很丝滑👍 )
先画个图吧:
代码展示:
public static int removeDuplicates(int[] nums) {
// 定义快慢指针
int slow = 1;// 小心思
// fast 遍历数组
for(int fast = 0; fast < nums.length; fast++) {
if (nums[fast] != nums[slow - 1]){
nums[slow] = nums[fast];
slow++;
}
}
return slow;
}
那你猜为什么令slow = 1 💡,你试着写一写 slow = 0的时候比较一下
看图容易一些
代码如下:
public static int deleteRepeatVal(int[] nums) {
// 定义快慢双指针
int slow = 0;
int fast = 0;
while(fast < nums.length) {
if (nums[slow] == nums[fast]){
fast++;
}else {
nums[++slow] = nums[fast++];
}
}
return slow + 1;
}
总结
提示:双指针是常见的算法思想,建议重点掌握