算法学习:二分查找

  • 二分查找是非常基础的算法,但其并不简单,有很多细节需要掌握,因此自己结合资料进行了一些整理,在看完这些后,建议做几道给出的习题这样效果会更好。二分查找细节。

    1. 二分查找

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

示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

class Solution {
    public int search(int[] nums, int target) {
int left=0;
int right=nums.length-1;
while(left<=right){
    int mid=(right+left)/2;
    if(nums[mid]<target){
      left=mid+1;
    }else if(nums[mid]>target){
    right=mid-1;
    }else{
          return mid;
    }
  
}

return -1;
    }
}
    1. 第一个错误的版本

你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。 假设你有 n 个版本 [1, 2, …, n],你想找出导致之后所有版本出错的第一个错误的版本。你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数

示例 1:
输入:n = 5, bad = 4
输出:4
解释:
调用 isBadVersion(3) -> false
调用 isBadVersion(5) -> true
调用 isBadVersion(4) -> true
所以,4 是第一个错误的版本。

/* The isBadVersion API is defined in the parent class VersionControl.
      boolean isBadVersion(int version); */

public class Solution extends VersionControl {
    public int firstBadVersion(int n) {
        int left=0;
        int right=n;
        while(left<=right){
            int mid=left+(right-left)/2;
            if(isBadVersion(mid)){
                right=mid-1;
            }else{
                left=mid+1;
            }
        }
        return left;
    }
}
    1. 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。请必须使用时间复杂度为 O(log n) 的算法。

示例 1:输入: nums = [1,3,5,6], target = 5 输出: 2
示例 2:输入: nums = [1,3,5,6], target = 2 输出: 1

如果说定义 target 是在一个在左闭右开的区间里,也就是[left, right) 。

那么二分法的边界处理方式则截然不同。

不变量是[left, right)的区间,如下代码可以看出是如何在循环中坚持不变量的。

**大家要仔细看注释,思考为什么要写while (left < right), 为什么要写right = middle**class Solution {
    public int searchInsert(int[] nums, int target) {
int n=nums.length;
int left=0;
int right=n;//定义target在左闭右开的区间里,[left,right),target
while(left<right){//因为left==right的时候,在[left,right)是无效
    int mid=left+(right-left)/2;
if(nums[mid]>target){
    right=mid;
}else if(nums[mid]<target){
    left=mid+1;
}else{
    return mid;
}
//分别处理如下四种情况
//目标值在数组所有元素之前[0,0)
//目标值等于数组中某一个元素 return right即可
//目标值在数组所有元素之后的情况[left,right),return right+1

}
return right;
    }
}
时间复杂度:$O(logn)$
时间复杂度:$O(1)$

## 总结

希望通过这道题目,大家会发现平时写二分法,为什么总写不好,就是因为对区间定义不清楚。

确定要查找的区间到底是左闭右开[left, right),还是左闭又闭[left, right],这就是不变量。

然后在**二分查找的循环中,坚持循环不变量的原则**,很多细节问题,自然会知道如何处理了。
class Solution {
    public int searchInsert(int[] nums, int target) {
        int n = nums.length;

        // 定义target在左闭右闭的区间,[low, high]
        int low = 0;
        int high = n - 1;

        while (low <= high) { // 当low==high,区间[low, high]依然有效
            int mid = low + (high - low) / 2; // 防止溢出
            if (nums[mid] > target) {
                high = mid - 1; // target 在左区间,所以[low, mid - 1]
            } else if (nums[mid] < target) {
                low = mid + 1; // target 在右区间,所以[mid + 1, high]
            } else {
                // 1. 目标值等于数组中某一个元素  return mid;
                return mid;
            }
        }
        // 2.目标值在数组所有元素之前 3.目标值插入数组中 4.目标值在数组所有元素之后 return right + 1;
        return high + 1;
    }
}
public int searchInsert(int[] nums, int target) {
    for(int i = 0; i < nums.length;i++){
        if(nums[i] >= target){
            return i;
        }
    }
    return nums.length;
}
  • 1/x 的平方根
    实现 int sqrt(int x) 函数。

计算并返回 x 的平方根,其中 x 是非负整数。

由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去

class Solution {
public:  
  int mySqrt(int x) {  
 int l = 0, r = x, ans = -1;  
  while (l <= r) { 
int mid = l + (r - l) / 2;   
if ((long long)mid * mid <= x) {   
 ans = mid;        
 l = mid + 1;     
 }     
  else {       
  r = mid - 1;     
  }   
  }    
 return ans;  
 }
 };
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

little-peter

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值