目录
双指针的妙用(删除数组元素专题)
由于数组的元素是紧紧依靠在一起的,加入有空隙后面的元素就要整体向前移动。如果在中间位置插入元素,那么其后的元素都要整体向后移动。而很多算法题都需要多轮、大量移动元素,这就导致执行效率地下,解决该问题是数组算法的一个重要问题,其中一种非常好用的方式就是双指针思想。下面,我们将借用双指针来解决有关数组元素删除的专题。
题型一 移除所有数值等于val的元素
给定一个数组nums和一个值val,需要原地移除所有数值为val的元素,并返回移除后数组的新长度。但是要求:不使用额外的数组空间,必须仅用O(1)额外空间并原地修改输入数组。元素的顺序可以改变,不需要考虑数组中超出新长度后面的元素。
由于删除的时候,从删除位置开始的元素都要向前移动,所以如果有多值为val,则会导致反复移动,效率低下。故我们可以使用双指针方式,下面介绍三种方法。
方法一 快慢双指针
定义两个指针,分别为slow和fast,slow之前为数组有效部分,fast表示当前访问的元素。
这样遍历时,fast不断向后移动,遇到的情况有如下两种:
1)如果nums[fast]值不为val,则将其移动到nums[slow++]处,即赋值给nums[slow],slow指针右移
2)如果nums[fast]值为val,则fast继续向前移动,slow指针不动

实现代码如下:
public int removeElement(int[] nums, int val) {
// 快慢指针
int slow = 0;
for(int fast = 0; fast < nums.length; fast++){
// 若nums[fast]不为val,则将其赋给nums[slow],slow指针右移
if(nums[fast] != val){
nums[slow] = nums[fast];
slow++;
}
}
// 返回slow,即fast找到几个不为val的元素,即为新数组长度
return slow;
}
方法二 对撞双指针
该方法同样构建了两个指针,分别为最左边的left和最右边的right。其核心思想是从右侧找到不是val的值来顶替左侧是val的值,还是比较好理解的,我们看看下面的图就会很清楚了。

当lett == right的时候,left及其左侧就是删除掉2后的所有元素了。实现代码如下:
public int removeElement(int[] nums, int val) {
// 对撞指针
int left = 0;
int right = nums.length - 1;
for(left = 0; left <= right;){
// 当左侧为val,右侧不为val,则左右互换
if((nums[left] == val) && (nums[right] != val)){
int tmp = nums[left];
nums[left] = nums[right];
nums[right] = tmp;
}
// 若左侧不为val,left指针右移,右侧为val,right指针左移
if(nums[left] != val) left++;
if(nums[right]== val) right--;
}
return left;
}
方法三 对撞双指针+覆盖
基于上面的做法,我们做出如下改进。当nums[left]==val时,我们将nums[right]位置的元素覆盖nums[left], 继续循环,如果nums[left]还是等于val那就让nums[right]继续覆盖,否则才让left++,这样代码更加简洁优美,实现代码如下:
public int removeElement(int[] nums, int val) {
// 对撞指针和覆盖相结合
int right = nums.length - 1;
for(int left = 0; left <= right;){
// 只要nums[left]==val,就用nums[right]将其覆盖到不为val
if(nums[left] == val){
nums[left] = nums[right];
right--;
}else{
left++;
}
}
return right+1;
}
题型二 删除有序数组中的重复项
删除重复元素也是很多算法结构中经常出现的。在数组中其最基本的题便为,给定一个有序数组nums,让你原地删除重复出现的元素,使每个元素只出现一次,返回删除数组后数组的新长度。不要使用额外的数组空间,必须在原地修改,并在使用O(1)额外空间的条件下完成。
本题使用双指针最方便,思想其实都是一样的,一个指针负责数组遍历,一个指向有效数组的最后一个位置。为了减少不必要的操作,我们在此会做适当调整,例如令slow=1,因为第一关不需要删除,并且我们笔记的对象为nums[slow-1], 代码如下:
public int removeDuplicates(int[] nums) {
// 双指针
// sLow表示可以放新元素的位置,索引为0可不管
int slow = 1;
for(int fast = 0; fast < nums.length; fast++){
// fast 和 slow-1 对比, slow为调整的
// 若slow与fast所处值不同,则将fast的赋值给slow,因为fast所赋的一定不是重复的
if(nums[fast] != nums[slow - 1]){
nums[slow] = nums[fast];
slow++;
}
}
return slow;
}
那既然我们可以重复元素保留一个,那可不可以保留2个,3个或者k个?甚至一个都不要呢,其实也是可以的。其实思路是一模一样的,只是一个换成了k个,代码如下:
public int removeDuplicates(int[] nums) {
if(nums.length < 2){
return nums.length;
}
// 双指针
int slow = 2;
for(int fast = 2; fast < nums.length; fast++){
// 如果fast和slow-2一样,那么fast就一定和slow-1一样,那么就要换slow
// 当fast遍历到不等于slow-2时则替换掉slow,此时就只保留两个重复元素
if(nums[slow - 2] != nums[fast]){
nums[slow] = nums[fast];
slow++;
}
}
return slow;
}
本文介绍了如何使用双指针策略高效地移除数组中等于特定值的元素和删除有序数组中的重复项,同时保持原地操作且空间复杂度为O(1)。
739

被折叠的 条评论
为什么被折叠?



