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

目录

1. 数组中的双指针

2. 删除元素专题

2.1 删除数组中指定的所有值

2.1.1 解法一:快慢双指针

2.1.2 解法二:对撞型双指针

2.1.3 解法三:对撞双指针+覆盖

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

3. 元素奇偶移动专题

4. 数组轮转问题

5. 数组的区间问题


1. 数组中的双指针

当在数组[1,2,2,2,3,3,3,5,5,7,8]中想要删除重复的元素使其变成[1,2,3,5,7,8]时,使用每删除一个一个元素,数组后面的值就往前移一位太麻烦,且效率较低。

利用双指针思想,首先定义slow和fast两个指针,slow指针表示当前及之前的元素都是不重复的,fast则是一直往后面找不重复的元素,找到之后就将slow往后移一位,将arr[slow]=arr[fast]即可,然后fast继续往后移,找不重复的元素。如下图,fast指针寻找的条件是arr[slow]!=arr[fast],所以slow在fast找到之前都不能移位置。

上面的为快慢指针,还有就是从两端一起走到中间的称为对撞型指针或相向指针。

最后一种是从中间向两端走的背向型指针。

2. 删除元素专题

2.1 删除数组中指定的所有值

力扣(LeetCode)27.移除元素

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

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

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

2.1.1 解法一:快慢双指针

可以利用上面的快慢指针思想,定义两个指针slow和fast都为0,因为此时的判断条件变成了nums[fast]和val之间的关系,所以此时的slow之前的元素都是有效部分,不包括slow指向的元素。

以val=2为例。

public static int removeElement(int[] nums, int val) {
        int slow=0;
        int fast=0;
        for (;fast < nums.length; fast++) {
            if (nums[fast]!=val){
                nums[slow]=nums[fast];
                slow++;
            }
        }
        return slow;
    }

2.1.2 解法二:对撞型双指针

核心思想是从右侧找到不是val的值来顶替左侧是val的值,以val=2为例。

当left<right时,left左侧就是删除2的所以元素了。

public static int removeElement_2(int[] nums, int val) {
        int left=0;
        int right=nums.length-1;
        while (left<=right){
            if (nums[left]==val&&nums[right]!=val){
                int temp=nums[left];//在这里将左右两个元素交换之后,下面的两个if就可在交换之后正常向中间走,否则right需额外指定++;
                nums[left]=nums[right];
                nums[right]=temp;
            }
            if(nums[left]!=val)left++;
            if (nums[right]==val)right--;
        }
        return  left;
    }

2.1.3 解法三:对撞双指针+覆盖

当nums[left]==val时,就将nums[right]位置的元素覆盖nums[left],此时left不++,因为nums[right]的值可能也是val

 public static int removeElement_3(int[] nums, int val) {
        int right=nums.length-1;
        int left=0;
        while (left <=right ){
            if (nums[left]==val){
                nums[left]=nums[right];
                right--;
            }else left++;
        }
        return left;
    }

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

力扣(LeetCode)26.删除有序数组中的重复项

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

考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:

  • 更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
  • 返回 k 。

解法:此题的解法就是解释双指针中所用到的解法,利用快慢指针。

public static int removeDuplicates(int[] nums) {
        int slow=0;
        int fast=1;
        while (fast<=nums.length-1){
            if (nums[slow]!=nums[fast]){
                slow++;
                nums[slow]=nums[fast];
            }
            fast++;
        }
        return slow+1;
    }

3. 元素奇偶移动专题

力扣(LeetCode)905.按奇偶排序数组

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

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

示例 :

输入:nums = [3,1,2,4]
输出:[2,4,3,1]
解释:[4,2,3,1]、[2,4,1,3] 和 [4,2,1,3] 也会被视作正确答案。

利用双指针对撞型,即定义左右指针向中间靠拢,左指针找奇数,右指针找偶数,两者互换位置即可。

 public int[] sortArrayByParity_my(int[] nums) {
        int left=0;
        int right=nums.length-1;
        while (left<right){
            while (left<right && nums[left]%2==0)left++;     //找到奇数
            while (left<right && nums[right]%2==1)right--;   //找到偶数
            if (nums[left]%2 > nums[right]%2){  //奇数和偶数互换位置
                int temp=nums[left];
                nums[left]=nums[right];
                nums[right]=temp;
            }
        }
        return nums;

    }

4. 数组轮转问题

力扣(LeetCode)189.轮转数组

给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

示例 :

输入: nums = [1,2,3,4,5,6,7], k = 3
输出: 
[5,6,7,1,2,3,4]

解释:
向右轮转 1 步: 
[7,1,2,3,4,5,6]

向右轮转 2 步: 
[6,7,1,2,3,4,5]
向右轮转 3 步: 
[5,6,7,1,2,3,4]

之前在链表中也遇到了类似的题,在链表中可以利用双指针截断前后链表再连接,现在在数组中可以先将整个数组反转之后,再将指定区间的数组反转即可达到目标。

例如[1,2,3,4,5,6,7]中,先反转为[7,6,5,4,3,2,1],再将前三个[7,6,5]和后4个[4,3,2,1]反转为[5,6,7]和[1,2,3,4]即可。

public void rotate_my(int[] nums, int k) {
        k%=nums.length;
        reverse_my(nums,0,nums.length-1);
        reverse_my(nums,0,k-1);
        reverse_my(nums,k,nums.length-1);

    }
    public  void reverse_my(int[] nums, int start, int end) {//反转
        while (start<end){
            int temp=nums[start];
            nums[start]=nums[end];
            nums[end]=temp;
            start++;
            end--;
        }
    }

5. 数组的区间问题

力扣(LeetCode)228. 汇总区间

给定一个  无重复元素 的 有序 整数数组 nums 。

返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说,nums 的每个元素都恰好被某个区间范围所覆盖,并且不存在属于某个范围但不属于 nums 的数字 x 。

列表中的每个区间范围 [a,b] 应该按如下格式输出:

  • "a->b" ,如果 a != b
  • "a" ,如果 a == b

示例 1:

输入:nums = [0,1,2,4,5,7]
输出:["0->2","4->5","7"]
解释:区间范围是:
[0,2] --> "0->2"
[4,5] --> "4->5"
[7,7] --> "7"
public List<String> summaryRanges_my(int[] nums) {
        List<String> res=new ArrayList<>();
        int slow=0;
        for (int fast = 0; fast < nums.length; fast++) {
            if (fast+1==nums.length || nums[fast]+1!=nums[fast+1]){
                StringBuilder sb=new StringBuilder();
                sb.append(nums[slow]);
                if (slow!=fast){ //当是单独的一个数字时就不需要箭头
                    sb.append("->").append(nums[fast]);
                }
                res.add(sb.toString());
                slow=fast+1;
            }
        }
        return res;
    }

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值