算法通关村——双指针的妙用

文章介绍了双指针思想在数组和字符串问题中的应用,包括如何删除重复元素和原地移除特定值的元素。通过快慢指针或对撞指针的方式,高效解决数组中的问题,如LeetCode的27题和26题。同时,展示了如何使用双指针对偶数和奇数进行排序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 双指针思想

        双指针就是两个变量,不一定真的是指针。双指针思想适用于处理数组、字符串等场景问题。

        例如:从下列序列中删除重复元素[1,2,2,2,3,3,3,5,5,,7,8],重复元素只保留一个。删除之后的结果应该为[1,2,3,5,7,8]。可以在删除第一个2时将其后面的元素整体向前移动一次,删除第二个2时,再将其后的元素整体向前移动一次,处理后面的3和5都一样的情况,这就导致需要执行5次大量移动才能完成,效率较低。

        使用双指针可以方便的解决这个问题,如图:

         首先定义两个指针slow、fast。

         slow表示当前位置之前的元素都是不重复的,而 fast 则一直向后找,直到找到与 slow 位置不一样的。找到之后就将 slow 向后移动一个位置,并将 arr[fast] 复制给 arr[slow] ,之后 fast 继续向后找,循环执行。找完之后 slow 以及之前的元素就都是单一的了。这样就可以只用一轮移动解决问题。

        如上这种一个在前一个在后的方式也称为快慢指针,有些场景需要从两端向中间走,这种成为对撞指针或者相向指针。

        还有一种背向型指针,就是从中间向两边走。

2. 删除元素专题

2.1 原地移除所有数值等于 val 的元素

LeetCode27:给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

要求:

1.不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。

2.元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

例子1:

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]

例子2:

输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]

关键点:如果有很多值为val的元素的时候,如何避免反复向前移动?

快慢双指针

定义两个指针slow和fast,初始值都是0.

slow之前的位置都是有效部分,fast表示当前要访问的元素。

这样遍历的时候,fast不断向后移动:

  • 如果 num[fast] 的值不为 val ,则将其移动到 nums[slow++] 处。
  • 如果 nums[fast] 的值为 val ,则 fast 继续向前移动,slow 先等待。

这样,前半部分是有效的,后半部分是无效的。

 /**
     * 
     * @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;
    }

2.2 删除有序数组中的重复项

LeetCode26:给你一个 升序排列 的数组 nums ,请你原地删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的相对顺序应该保持 一致 。然后返回 nums 中唯一元素的个数。

示例1:
输入:nums = [1,1,2]
输出:2, nums = [1,2,_]
示例2:
输入:nums = [0,0,1,1,1,2,2,3,3,4] 输出:5, nums = [0,1,2,3,4]

一个指针负责数组遍历,一个指向有效数组的最后一个位置。并且令 slow = 1,并且比较的对象换做 nums[slow-1] ,如图:

最后返回 slow 的值 2,表示修改后的数组长度为2。

最终结果为:[1,2]。

ps:上述操作中,并没有真正删除数组中的元素,知识通过slow的位置来标识新数组的长度,原数组中的值仍然存在,只是在 slow 之后的位置不在有有效值。 

代码如下:

public  static int removeDuplicates(int[] nums) {
        //slow表示可以放入新元素的位置,索引为0的元素不用管
        int slow = 1;
        //循环起到了快指针的作用
        for (int fast = 0; fast < nums.length; fast++) {
            if (nums[fast] != nums[slow - 1]){
                nums[slow] = nums[fast];
                slow++;
            }
        }
        return slow;
    }

进阶版:

给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
 

示例1:

输入:nums = [1,1,1,2,2,3]

输出:5, nums = [1,1,2,2,3]

示例2:

输入:nums = [0,0,1,1,1,1,2,3,3]

输出:7, nums = [0,0,1,1,2,3,3]

以示例1为例,步骤如图:

代码实现:

 public static int removeDuplicatesPlus(int[] nums) {
        //最大重复次数2次
        int maxRepeat = 2;
        //慢指针slow 指向索引为1的位置
        int slow = maxRepeat -1;
        for (int fast = maxRepeat; fast < nums.length; fast++) {
            //nums[fast] != nums[slow - maxRepeat + 1]
            //保证区间[0,slow] 中元素最多不会超过2次
            if (nums[fast] != nums[slow - maxRepeat +1]){
                //先扩展区间
                slow++;
                //再赋值
                nums[slow] = nums[fast];
            }
        }
        return slow + 1;
    }

3. 元素奇偶移动专题

LeetCode905,按奇偶排序数组。

给你一个整数数组 nums,将 nums 中的的所有偶数元素移动到数组的前面,后跟所有奇数元素。

返回满足此条件的 任一数组 作为答案。

例如:

输入:[3,1,2,4]

输出:[2,4,3,1]

输出 [4,2,3,1] , [2,4,1,3] , [4,2,1,3] 也会被接口

最简单实现:使用一个临时数组,第一遍查找并将所有的偶数复制到新数组的前部分,第二遍查找并复制所有的奇数到数组后部分。但是空间复杂度较高。

对撞型双指针方法,如图:

代码如下:

public static int[] sortArrayByParity(int[] nums) {
        int left = 0, right = nums.length - 1;
        while (left < right) {
            if (nums[left] % 2 > nums[right] % 2){
                int temp = nums[left];
                nums[left] = nums[right];
                nums[right] = temp;
            }
            if (nums[left] % 2 == 0) left++;
            if (nums[right] % 2 == 1) right--;
        }
        return nums;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值