训练营0921| leetcode以二分查找为例的双指针系列题

本文详细介绍了二分查找算法的实现原理及多种应用场景,并深入探讨了双指针技巧在数组操作中的高效应用,包括移除元素、删除重复项及移动零等常见问题的解决方法。

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

目录

704.二分查找

35.搜索插入位置

34.在排序数组中查找元素第一次和最后一次出现的位置

27.移除元素

 26. 删除有序数组中的重复项

 283.移动零


704.二分查找

二分查找思路:

首先数组必须有序,在有序的数组中去寻找一个数,利用双指针的思想,mid索引处的值与target值做比较,相应的去缩哪个边界。

class Solution {
    //二分查找
    public int search(int[] nums, int target) {
        int left=0,right=nums.length-1;//双指针
        while(left<=right){
            int mid=(left+right)>>>1;//防止地址越界
            if(nums[mid]==target){
                return mid;
            }else if(nums[mid]<target){//mid索引处的值比目标值小,则在右边,缩左边界
                left=mid+1;
            }else {//在左边,缩right
                right=mid-1;
            }
        }
        return -1;//没找着
    }
}

数组理论基础

 而今天在营里所得还是比较多,比如我写的时候就不会考虑为什么肯定是left<=right呢,因为我根本就没有对边界值有过考虑,一直写的就是这样,这可能也是为什么刷到双指针的变题,就感觉有些吃力的程度。

二分查找文字解析

第一种写法,我们定义 target 是在一个在左闭右闭的区间里,也就是[left, right] (这个很重要非常重要)

区间的定义这就决定了二分法的代码应该如何写,因为定义target在[left, right]区间,所以有如下两点:

  • while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
  • if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1

如果说定义 target 是在一个在左闭右开的区间里,也就是[left, right) ,那么二分法的边界处理方式则截然不同。

有如下两点:

  • while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
  • if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]

class Solution {
    /**
     *二分查找--最后停的位置
     * @param nums
     * @param target
     * @return 找不到返回他应该按顺序插入的位置,找到返回他的索引
     */
    public int searchInsert(int[] nums, int target) {
        int left=0,right= nums.length-1;
        int mid=0;
        while(left<=right){
             mid=(left+right)>>>1;
             if(nums[mid]==target){
                 return mid;
             }else if(nums[mid]<target){
                 left=mid+1;
             }else {
                 right=mid-1;
             }
        }
        return right+1;
    }
}

就是在二分的基础上起码模拟两次最后停的时候right,left,mid值。

34.在排序数组中查找元素第一次和最后一次出现的位置
class Solution {
    /**
     *
     * @param nums
     * @param target
     * @return 出现的范围数组,起始+终止,无的话返回[-1,-1]
     */
    public int[] searchRange(int[] nums, int target) {
        int[] ans=new int[2];
        int left=0,right=nums.length-1;
        int first=-1,second=-1;//记录ans数组里面第一个,第二个值;
        while(left<=right){
            int mid=(left+right)>>>1;
            if(nums[mid]==target){
                for (int i = left; i <=mid; i++) {
                    if(nums[i]==target){
                        //找前面,找到了就break,最坏就是mid所在的地方,是ans[0]
                        first=i;
                        break;
                    }
                }
                if(first==-1){//for循环无法执行,是整体会在上一次mid前面,所以第一个值是mid所在的地方
                    first=mid;
                }
                for (int i = right; i >mid ; i--) {
                    //找后面,找到了就break,找不到的话second值是-1的话,表明他后面没有
                    if(nums[i]==target){
                        second=i;
                        break;
                    }
                }
                if(second==-1){
                    second=mid;
                }
                break;
            }else if(nums[mid]<target){
                left=mid+1;
            }else {
                right=mid-1;
            }
        }
        ans[0]=first;ans[1]=second;
        return ans;
    }
}

一开始有想要是不是要把上一次的mid值保存成temp临时变量,尽可能的去缩一下左边界和右边界,减少一下时间复杂度,但是mid值往前找和往后找的两种方式对temp变量怎么找还是有一些不同的界定。

不过总体时间用时还行,所以从头再搜和从后搜没有特别大的区别。

 注:从后往前找的时候要找最后一个出现的,用i - -

27.移除元素

移除元素讲解

很久之前刷过记得是用双指针但是怎么用双指针还是忘记了,只记得需要使用快慢指针,但是指针走几步还是记不清了,所以最后还是用的暴力方法

class Solution {
    /**
     * 移除nums数组中值为val的元素(原地移除)
     * @param nums
     * @param val
     * @return 新数组长度
     */
    public int removeElement(int[] nums, int val) {
       int len=nums.length;
       int i=0;
       while (i<len){
           if(nums[i]==val){
               //后面的值覆盖
               for (int j = i; j <len-1 ; j++) {
                   nums[j]=nums[j+1];
               }
               len--;
           }else {
               i++;
           }
       }
       return len;
    }
}

看题解之后大概得出思路

class Solution {
    /**
     * 双指针--->
     * slow和fast同时移动
     * @param nums
     * @param val
     * @return
     */
    public int removeElement(int[] nums, int val) {
        int slow=0,fast=0;
        while (fast<nums.length){
            if(nums[fast]!=val){
                nums[slow++]=nums[fast];
            }
            fast++;
        }
        return slow;
    }
}

快指针去找新数组中需要的元素,而慢指针维护新数组的边界位置。

2024补:用双指针,在两侧 left和right,left去找等于val的right找不等于val的,找到即swap;最后处理边界值,返回right值,如果right因为数组单一元素;

class Solution {
    public int removeElement(int[] nums, int val) {
        if (nums.length <= 0) {
            return 0;
        }
        int left = 0, right = nums.length - 1;
        while (left <= right) {
            if (nums[left] == val && nums[right] != val) {
                swap(nums,left,right);
            }
            if (nums[left] != val) {
                left++;
            }
            if (nums[right] == val) {
                right--;
            }
        }
        return right == 0 && nums[0] == val ? right : right + 1;
    }

    private void swap(int[]nums, int a, int b) {
        int temp = nums[a];
        nums[a] = nums[b];
        nums[b] = temp;
    }
}

 26. 删除有序数组中的重复项

双指针感觉跟动态规划差不多反正写的时候一定要搞清楚slow,fast指针是干嘛的

class Solution {
    /**
     * 因为有序所以相等的肯定相邻,slow保留新数组里面的值即每个数都不相等的数,而fast去扫哪些数不等
     * @param nums
     * @return 移除相同的元素后数组长度
     */
    public int removeDuplicates(int[] nums) {
        int slow=0,fast=1;
        for ( fast = 1; fast < nums.length ; fast++) {
            if(nums[fast]!=nums[slow]){//扫到了不相等的数,与slow的下一位交换
                nums[++slow]=nums[fast];
            }
        }
        return slow+1;//返回长度
    }
}
 283.移动零

slow是去先找第一个为0的数组的地方,fast不为0的处并且在他后面,就要交换。

class Solution {
    /**
     * 移动数组里面0元素到末尾
     * 双指针,slow指针指向数组里面第一个为0的地方,fast去找不为0的和其交换
     * @param nums
     */
    public void moveZeroes(int[] nums) {
        int slow=0,fast=1;
        while (fast<nums.length){
           while (nums[slow]!=0){
               if(slow== nums.length-1){
                   break;
               }
               slow++;
           }
           if((slow<fast)&&nums[fast]!=0){
               swap(nums,slow,fast);
               slow++;
           }
           fast++;
       }
    }
    public void swap(int[] arr,int a,int b){
        arr[a]=arr[a]^arr[b];
        arr[b]=arr[a]^arr[b];
        arr[a]=arr[a]^arr[b];
    }
}

注:边界值的判定,一开始找不到为0的slow直接break出来。跳出多重循环,在外面加标识,break这个标识、、

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值