代码随想录算法训练营第一天:704二分查找 + 27移除元素(学习时长两小时)

文章详细介绍了二分查找算法在搜索、移除元素以及寻找排序数组中元素位置等场景的应用,强调了区间处理、防止溢出和时间复杂度优化的细节,并提供了不同情况下的代码实现,包括闭区间和左闭右开区间的二分查找。此外,还探讨了原地修改数组和使用双指针策略解决数组问题的方法。
摘要由CSDN通过智能技术生成

704二分查找(注意细节)

题目要求

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1

思路

数据结构中小low大high;时间复杂度为O(log n)

细节

一刷

注意while中的符号<=;注意mid值的定义,为什么要使用减法而不是加法

因为原本学习的相加/2,两个int类型加法有可能会导致溢出,为了防止溢出,先用减法再用除法最后用加法,十分巧妙

二刷

对于return的理解不深刻,return可以看作是程序的结束口。详细细节见注释。

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

代码一(闭区间)


class Solution {
    public int search(int[] nums, int target) {
        int low = 0, high = nums.length - 1;
        while(low <= high){//有等号是因为目前使用的是闭区间,low=high是可以成立的
            int mid = low + ((high - low) / 2 );//这一步为什么能够防止溢出
            if(nums[mid] < target){//因为mid处的值已经小于target了,这个值以后永远取不到,所以+1
                low = mid + 1;
            }else if(nums[mid] > target){
                high = mid - 1 ;
            }else{
                return mid;
            }
        }
        return -1;
    }
}




代码二(左闭右开区间)

class Solution {
    public int search(int[] nums, int target) {
        int i = 0;
        int j = nums.length;
        while( i < j ){//左闭右开区间,i与j无法相等(不满足数学里区间的定义)[1,1)不成立
            int mid = i + ((j - i ) >> 1) ;
            if(nums[mid] < target){
                i = mid + 1;
            }else if(nums[mid] > target){
                j = mid;//这里不-1是因为要满足左闭右开区间的条件。
            }else{
                return mid;
            }
        }
        return -1;
    }
}

27移除元素

题目要求

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

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

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

思路

注意数组元素的删除其本身位置不会删除,原理应该是后面元素向前移动进行覆盖,原数组的长度是不变的。在使用高级编程语言后,会显示长度变化,但本身不变。时间复杂度为O(n)

暴力法

既然删除元素要覆盖,写一个嵌套for循环,当找到对应值时进行覆盖(需要for循环从后面一个一个覆盖)。

注意当某次覆盖完成之后,第一层for循环i的位置需要向前回调,准备开始继续往后面新来的里面找val。

双指针

利用两个指针在原有数组基础上创建新数组。

快指针记录当前新数组需要的元素,慢指针记录当前新数组最后一个元素的下标;当不需要删除时,即获取到新数组需要的元素,二者一起向后移动,并将快指针记录的值赋给慢指针位置上;当需要删除时,快指针向后移动慢指针不动。

使用for循环来控制快指针自发地向后移动。

代码一

class Solution {
    public int removeElement(int[] nums, int val) {
        //暴力法怎么删——依次遍历遇到等于val的删除,并且将数组长度-1
        //删除的具体操作呢?覆盖
        int size = nums.length;
        for(int i = 0; i < size; i ++){
            if(nums[i] == val){
                for(int j = i + 1; j < size; j ++){
                    nums[j - 1] = nums[j];
                }
                //覆盖完这个元素之后,i需要向后退。因为还要去找新的等于val的位置
                i --;
                size --;
            }
        }
        return size;//时间复杂度是n方
    }
}

代码二

class Solution {
    public int removeElement(int[] nums, int val) {
        //方法二:快慢指针
        int slow = 0;
        for(int fast = 0;fast < nums.length; fast ++){
            if(nums[fast] != val){
                //遇到需要的元素,即非val的其他元素
                nums[slow ++] = nums[fast];
            }
        }
        return slow;//最终返回的值也很巧妙,因为slow记录着新数组的最终下标值(这个下标值还是在原数组的基础上的)
    }
}

35搜索插入位置(分情况讨论最终的返回值)

题目要求

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

思路

有序数组使用二分查找,但是最终的返回值很巧妙。需要分类讨论找到最后的规律

代码

class Solution {
    public int searchInsert(int[] nums, int target) {
        int low =0, high = nums.length - 1;
        //先写个二分查找
        while(low <= high){
            int mid = low + (( high - low) / 2);
            if(nums[mid] < target){
                low = mid + 1;
            }else if(nums[mid] > target){
                high = mid - 1;
            }else{
                return mid;
            }
        }
        //分情况讨论[1,3,5,6]
        //如果值在数组的外面的话比如0.最后high=-1,low=0,mid=0,插入位置为0
        //如果插入元素在数组右边外面,最后low=4,high=3,mid=3,插入位置为4
        //插入元素在数组之间的话,2,high=0,low=1,mid=0,插入位置为1;4high=1,low=2,mid=2,插入位置为2
        return high + 1;//好像还能够返回low
    }
}

34排序数组中查找元素的第一个和最后一个位置

题目要求

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

思路

1使用两个二分查找来分别找到左右边界,其中找的过程十分巧妙,最后分情况讨论返回值

2先用二分查找找到所在位置,如果在数组里那就继续,如果不在数组里直接返回-1;继续找的时候进行左右的遍历,满足条件向左右不断地走

代码

方法一(找左右边界设置的border很巧)

class Solution {
    public int[] searchRange(int[] nums, int target) {
        //本道题的思路是利用二分查找去分别找到左右边界,并且将最终结果分情况讨论
        int rightBorder = searchRight(nums,target);
        int leftBorder = searchLeft(nums,target);
        //分情况讨论
        //1目标值在数组范围之外,这种情况会导致一个结果就是rightborder和left全是-2,因为一直在往一个方向移动,即一直在某个if循环当中
        if(rightBorder == -2 || leftBorder == -2){
            return new int [] {-1,-1};//注意返回数组怎么返回
        }
        //2目标值就在数组某个元素,直接返回左右边界值,注意函数里面边界值是要比正确值偏移1的
        if(rightBorder - leftBorder > 1){
            return new int [] {leftBorder + 1 ,rightBorder - 1};
        }
        //3目标值在数组之间但不是其中的元素,直接return即可
        
        return new int [] {-1,-1};//注意这里的返回要新创建一个数组
    }
    //寻找右边界
    int searchRight(int[] nums,int target){
        int low= 0,high = nums.length - 1;
        int rightBorder = -2;//记录右边边界值的下一个位置
        while(low <= high){
            int mid = low + ((high - low ) / 2);
            if(nums[mid] > target){
                   high = mid - 1;
            }else{//既包含了相等又包含了小于
                low = mid + 1;
                rightBorder = low;//low不断地在向左边移动,最后移到的位置要比high大1
            }
        }
        return rightBorder;
    }
    //寻找左边界
    int  searchLeft(int [] nums,int target){
        int low= 0,high = nums.length - 1;
        int leftBorder = -2;//记录右边边界值的下一个位置
        while(low <= high){
            int mid = low + ((high - low ) / 2);
            if(nums[mid] >= target){//这里直接包含了大于等于
                   high = mid - 1 ;
                   leftBorder = high;
            }else{
                low = mid + 1;
                
            }
        }
        return leftBorder;
    }
}


方法二(更好理解)

class Solution {
    public int[] searchRange(int[] nums, int target) {
       int index = BinarySearch(nums , target);//
       if(index == -1){
           //没有找到,即目标值不在数组当中
           return new int[]{-1,-1};
       }
       //找到了开始向左右遍历
       int left = index;
       int right = index;
       while(left-1 >=0 && nums[left -1 ] == nums[index]){//注意这里的left>1,因为里面left--,最终可以减到0跳出循环,如果改成>0则会减到-1越界
            left --;
       }
       while(right < nums.length -1 &&nums[right+1] == nums[index]){//注意这里范围不要越界
           right ++;
       }
       return new int[]{left,right};
}
/**二分查找 */
    public int BinarySearch(int[] nums , int target){
        int low=0,high=nums.length - 1;
        while(low<=high){
            int mid = low+((high-low) / 2);
            if(nums[mid] < target){
                low = mid + 1;
            }else if(nums[mid] > target){
                high = mid -1;
            }else{
                return mid;
            }
        }
        return  -1;//没有找到
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值