算法通关村 —— 双指针的妙用(删除数组元素专题)

目录

双指针的妙用(删除数组元素专题)

题型一 移除所有数值等于val的元素

方法一 快慢双指针

方法二 对撞双指针

方法三 对撞双指针+覆盖

题型二 删除有序数组中的重复项


双指针的妙用(删除数组元素专题)

由于数组的元素是紧紧依靠在一起的,加入有空隙后面的元素就要整体向前移动。如果在中间位置插入元素,那么其后的元素都要整体向后移动。而很多算法题都需要多轮、大量移动元素,这就导致执行效率地下,解决该问题是数组算法的一个重要问题,其中一种非常好用的方式就是双指针思想。下面,我们将借用双指针来解决有关数组元素删除的专题。

题型一 移除所有数值等于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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值