代码随想录算法训练营第一天| 704. 二分查找、27. 移除元素

本文探讨了二分查找算法的实现细节,包括左闭右闭与左闭右开两种形式,以及如何处理溢出问题。同时,分析了移除元素问题的双指针解决方案,比较了替换与覆盖两种策略。文章还提及了相关附加题目,如搜索插入位置和查找元素范围,强调了理解和优化算法的重要性。
摘要由CSDN通过智能技术生成

文章目录


前言

数组基础;二分法;双指针;暴力;数组覆盖


一、704. 二分查找


下面是三段代码,第一个是最初的,后两个为学习后的;难点有:

  • 未考虑到 left + right 可能会溢出的问题;
  • 在<与<=之间游移不定;
  •  >> 与 + 的运算优先级不明确
//初代码
class Solution {
    public int search(int[] nums, int target) {
        int len = nums.length;
        int first = 0,last = len-1;
        while(first<=last){
            // int mid = len<<2;
            int mid = (last-first)/2 + first;
            int num = nums[mid];
            if(target==num){
                return mid;
            }
            if(target<nums[mid]){
                last = mid -1;
            }
             if(target>nums[mid]){
                first = mid+1;
            }
        }

        return -1;
    }
}

//左闭右闭

class Solution{
    public int search(int[] nums, int target){
        int l = 0; 
        int r = nums.length-1;

        while(l<=r){
            int mid = ((r-l)>>1) +l; //+的优先级高于<<
            if(nums[mid] == target){
                return mid;
            }
            if(nums[mid] > target){
                r = mid-1;
            }
            if(nums[mid] < target){
                l = mid+1;
            }
    }
    return -1;
}
}

//左闭右开
class Solution{
    public int search(int[] nums,int target){
        int l = 0;
        int r = nums.length;
        while(l<r){
            int mid = ((r-l) >> 1) + l;
            if(nums[mid] == target){
                return mid;
            }
            if(nums[mid] < target){
                l = mid +1;
            }
            if(nums[mid] > target){
                r = mid;
            }
        }
        return -1;
    }
}
  • 在数组的左闭右闭与左闭右开中,(左开右闭不常用)

       判断条件是否存在等号,取决于能否让【left,right】在left=right的时候成立;

       如:left=1,right=1,【1,1】成立,那么左闭右闭也成立;

引:

  • 其实二分还有很多应用场景,有着许多变体,比如说查找第一个大于target的元素或者第一个满足条件的元素,都是一样的,根据是否满足题目的条件来缩小答案所在的区间,这个就是二分的本质。另外需要注意,二分的使用前提:有序数组
  • 二分的最大优势是在于其时间复杂度是O(logn),因此看到有序数组都要第一时间反问自己是否可以使用二分。
  • 关于二分mid溢出问题解答:
    • mid = (l + r) / 2时,如果l + r 大于 INT_MAX(C++内,就是int整型的上限),那么就会产生溢出问题(int类型无法表示该数)
    • 所以写成 mid = l + (r - l) / 2或者 mid = l + ((r - l) >> 1) 可以避免溢出问题
  • 对于二进制的正数来说,右移x位相当于除以2的x几次方,所以右移一位等于➗2,用位运算的好处是比直接相除的操作快
  • fast < nums.size() 和 fast <= nums.size()-1 没什么区别,为什么第二个会在空数组时报数组越界的错误?
    vector的size()函数返回值是无符号整数,空数组时返回了0,再减个一会溢出

二、27. 移除元素

代码共两段如下:

没有暴力的代码,第一个仍是双指针,采用替换的策略;第二是快慢指针,和第一个的区别是覆盖,而非替换;

class Solution {
    public int removeElement(int[] nums, int val) {
        int len = nums.length;
        int l = 0;
        int r = len-1;

        //  左闭右闭
        while(l <= r){
            if(nums[l] == val){
                int m = nums[l];
                nums[l] = nums[r]; 
                nums[r] = m;

                r--;
            }
            if(nums[l] != val){
                l++;
            }
        }
        return l;
    }
}

//快慢指针

class Solution{
    public int removeElement(int[] nums,int val){
        int fast = 0;
        int slow =0;

        for(;fast<= nums.length-1;fast++){
            if(nums[fast] != val){
                nums[slow] = nums[fast];
                slow++;
            }
        }

        return slow;
    }
}

引:

  • 快指针可以理解成在旧数组中找非目标元素,然后赋值给慢指针指向的新数组,虽然都指向一个数组
  • 关于二分法和移除元素的共性思考
    这两题之间有点类似的,他们都是在不断缩小 left 和 right 之间的距离,每次需要判断的都是 left 和 right 之间的数是否满足特定条件。对于「移除元素」这个写法本质上还可以理解为,我们拿 right 的元素也就是右边的元素,去填补 left 元素也就是左边的元素的坑,坑就是 left 从左到右遍历过程中遇到的需要删除的数,因为题目最后说超过数组长度的右边的数可以不用理,所以其实我们的视角是以 left 为主,这样想可能更直观一点。用填补的思想的话可能会修改元素相对位置,这个也是题目所允许的。

三、附加题目

35.搜索插入位置

难点在于理清题目,找到规律:

 // 分别处理如下四种情况
        // 目标值在数组所有元素之前  [0, -1]
        // 目标值等于数组中某一个元素  return middle;
        // 目标值插入数组中的位置 [left, right],return  right + 1
        // 目标值在数组所有元素之后的情况 [left, right], 因为是右闭区间,所以 return right + 1

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

难点在于理解左右边界,随想录的java解将等于与小于或大于放在了一起,分开或许会更易理解,附上Leecode,爱做梦的鱼的代码

 
 // 两次二分查找,分开查找第一个和最后一个
  // 时间复杂度 O(log n), 空间复杂度 O(1)
  // [1,2,3,3,3,3,4,5,9]
  public int[] searchRange2(int[] nums, int target) {
    int left = 0;
    int right = nums.length - 1;
    int first = -1;
    int last = -1;
    // 找第一个等于target的位置
    while (left <= right) {
      int middle = (left + right) / 2;
      if (nums[middle] == target) {
        first = middle;
        right = middle - 1; //重点
      } else if (nums[middle] > target) {
        right = middle - 1;
      } else {
        left = middle + 1;
      }
    }

    // 最后一个等于target的位置
    left = 0;
    right = nums.length - 1;
    while (left <= right) {
      int middle = (left + right) / 2;
      if (nums[middle] == target) {
        last = middle;
        left = middle + 1; //重点
      } else if (nums[middle] > target) {
        right = middle - 1;
      } else {
        left = middle + 1;
      }
    }

    return new int[]{first, last};
  }

总结

感悟很多,数组为算法开了个好头。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值