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

249 阅读3分钟

删除元素专题

白银挑战:双指针思想及其应用

1.原地删除数组中所有元素等于val的值

解题思路:利用对撞双指针, left初始为数组下标为0的位置,right初始为数组最后一个位置;

如果当前left指向的元素等于val,且right指向的元素不等于val,那么将数组中left,right位置的值进行交换,然后left++;

如果当前left指向的元素不等于val,left++即可;

如果当前right指向的元素等于val,right--即可;

这样遍历完后,left左边的数组元素全部都是不等于val的值;

指针变换过程如图所示:

image.png

image.png

left左边的数组长度即是需要返回的长度;

代码如下:

public static int delVal(int[] arr, int val) {
    if (arr.length == 0) {
        return -1;
    }
    int left = 0;
    int right = arr.length - 1;
    while (left <= right) {
        if (arr[left] == val && arr[right] != val) {
            swap(arr, left, right);
        }
        if (arr[left] != val) {
            left++;
        }
        if (arr[right] == val) {
            right--;
        }
    }
    return left;
}
​
// 通过异或运算来实现数组中两个位置的值的交换,前提是两个位置left,right不能相等;
// 举个例子:  a=10,b=8;
// 第一步: a= a^b=10^8;
// 第二步: b= a^b=(10^8)^8;  b=10;
// 第三步: a= a^b=(10^8)^10  a=8;
// a,b交换成功;
public static void swap(int[] arr, int left, int right) {
    arr[left] = arr[left] ^ arr[right];
    // 第二步相当于 arr[right]=(arr[left]^arr[right])^arr[right] <==> arr[right]=arr[left] ^ 0 =arr[left];
    // 0和任何数异或都得该数本身;
    arr[right] = arr[left] ^ arr[right];
    // 第三步相当于 arr[left]=(arr[left]^arr[right](arr[right]未改变))^arr[right](arr[right]已改变) <==> arr[left]=arr[right] ^ 0;
    arr[left] = arr[left] ^ arr[right];
}

2.删除有序数组中的重复项;(即每个数组元素只能在数组中出现一次)

对应 leetcode26. 删除有序数组中的重复项

解题思路:利用快慢指针

具体实现过程是让slow指针一开始指向数组 1 的位置,让fast指针可以从1位置出发,也可以从0位置出发;(我试过初始时fast=1,和fast=0,leetcode都让过)

这里为了方便,fast = 1;

如果在遍历数组的过程中,fast位置所对应的元素和 slow-1位置对应的元素不相等;那么就用fast指针位置的值覆盖掉slow指针位置所对应的值;

画图来理解:

假设给定一个数组 [1,2 ,2, 3, 3, 4, 5 ];

image.png

代码如下:

/*
 * 删除有序数组中的重复元素,使数组中每个元素只出现一次
 * 返回删除元素后的数组长度
 * */
public static int removeDuplicates(int[] arr) {
    if (arr.length == 0) {
        return -1;
    }
    int slow = 1;
    int fast = 1;
    while (fast < arr.length) {
        // 如果slow-1位置的值和fast位置的值不相等,那么就用arr[fast]覆盖掉arr[slow]的值;同时slow++,fast++;
        if (arr[slow - 1] != arr[fast]) {
            arr[slow] = arr[fast];
            slow++;
        }
        fast++;
    }
    return slow;
}

3.拓展:删除有序数组中的重复项 (但只需要删除数组中重复次数大于2的值)

只要掌握了上面题目的解题方法,那么这道题目也很容易想到解题思路;依然使用快慢指针;

但是slow,fast初始值应该为2,同时arr[fast] 应该和arr[slow-2] 进行比较,也就是fast指针所应的值和slow-2位置所对应的值作比较;

如果两个数不相等,则slow++,fast++;否则fast++;

只需要保证slow位置所对应的值和从它开始往左数第二个数保持不同即可;slow位置的值可以和它前一个数相同,但一定不能和它开始往前数第二个数相同,如果是这样,slow位置的值和前面两个数同时相同,那么重复次数就会大于2;不符合题意;

代码如下:

  public static int removeDuplicates(int[] arr) {
        if (arr.length <= 2) {
            return arr.length;
        }
        int slow = 2, fast = 2;
        while (fast < arr.length) {
            if (arr[slow - 2] != arr[fast]) {
                arr[slow] = arr[fast];
                slow++;
            }
            fast++;
        }
        return slow;
    }

是不是和上一题很像,那我们就可以总结出,如果需要保证数组中重复出现的数可以为 k次,那么我们只要让fast指针所对应的值和slow-k所对应值相比较即可如果不相同,slow,fast同时往后动,否则,fast单独移动;