LeetCode算法题1:二分查找及扩展应用


前言

      Leetcode算法系列:https://leetcode-cn.com/study-plan/algorithms/?progress=njjhkd2

      简单介绍总结一下二分查找相关的算法题:

一、二分查找

      题目链接:https://leetcode-cn.com/problems/binary-search/

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

      思路:直接采用通用的二分查找算法即可:

class Solution {
    public int search(int[] nums, int target) {
        int start=0,end=nums.length-1,mid;
        
        while(start<=end){
            mid=(start+end)/2; // 最好改为:mid = start+(end-start)/2;
            if(nums[mid]==target)
                return mid;
            else if(nums[mid]<target)
                start=mid+1;
            else
                end=mid-1;
        }
        return -1;
    }
}

      1,取 mid 值时,采用 mid = start+(end-start)/2; 可以避免当 start 和 end 很大时造成的整数(int)越界。
      2,循环结束条件为 start<=end,所以在 while 循环退出时有 start>end,(在此为 start=end+1)。
      3,二分查找的时间复杂度为 O(logn),因为每次循环都会将待处理数组的规模缩小一半。

二、第一个错误的版本

      题目链接:https://leetcode-cn.com/problems/first-bad-version/

      题目描述:你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。

假设你有 n 个版本 [1, 2, …, n],你想找出导致之后所有版本出错的第一个错误的版本。

你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。

      思路:保持之间的二分查找框架不变,修改循环退出条件为当前版本正确且上一个版本错误,

public class Solution extends VersionControl {
    public int firstBadVersion(int n) {
        if(isBadVersion(1))
            return 1;
        int start=2,mid; //区间为[2,n].
        while(start<=n){  //还是按照二分查找的思路来做
            mid=start+(n-start)/2;
            if(!isBadVersion(mid))   //缩小区间到[mid+1,n]
                start=mid+1;
            else if(!isBadVersion(mid-1))    //如果mid为true,mid-1为false,返回mid即可
                return mid; 
            else    //如果mid-1不是false,缩小区间到[start,mid-1]
                n=mid-1;
        }
        return -1;
    }
}

      缺点时要调用两次 isBadVersion函数。

      Version2:修改循环判定条件为start<end,在循环退出时的 start 或 end 为我们感兴趣的下标值。并采用合理的缩小规模方式在循环内仅调用一次 isBadVersion 函数。如下:

 public class Solution extends VersionControl {
    public int firstBadVersion(int n) {
        int start=1,mid;
        while(start<n){
            mid=start+(n-start)/2;
            if(!isBadVersion(mid))
                start=mid+1;
            else
                n=mid;
        }
        return start;
    }
}

      上述算法看起来很巧妙,甚至成功的有点侥幸,因为在每次计算 mid 值时,由于向下取整,所以有时候会出现 mid 等于 start 的情况出现,而不会存在 mid 等于 n 的情况出现。因此令 n=mid;这样在每次循环时区间规模总会缩小,直至得到最终结果。

      在缩小区间时,最好不要采用 start==mid,很有可能区间不会缩小,陷入死循环。比如修改问题为:找到最后一个正确的版本。会出现 start=mid 和 end=mid-1。由于有 start=mid 存在,在循环时区间规模不一定会缩小,所以可能会陷入死循环。

三、搜索插入位置

      题目链接:https://leetcode-cn.com/problems/search-insert-position/

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

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

      思路:采用通用的二分查找框架,如果目标值存在于数组中,由 mid 给出其返回下标;如果不存在的话,由 start 或 end+1 给出返回下标。

class Solution {
    public int searchInsert(int[] nums, int target) {
        int start=0,end=nums.length-1,mid;
        while(start<=end){
            mid=start+(end-start)/2;
            if(nums[mid]==target)
                return mid;
            else if(nums[mid]<target)
                start=mid+1;
            else
                end=mid-1;
        }
        return start;   //关键点在于确定最后一个查找区间和target之间的关系。
        //return end+1;
    }
}

      由 start 或 end+1 给出返回值下标的原因在于:在一个不含目标值的升序数组中采用通用的二分查找算法查找 target 的位置,在循环结束后 end 处的值刚好小于 target;start 处的值刚好大于 target。 比如对于数组 [1,3,4,6,7],查找 5 所在位置:循环结束后 start =3,end = 2。

      Version2:修改循环判定条件为start<end,在循环退出时的 start 或 end 为我们感兴趣的下标值。如下:

    public int searchInsert(int[] nums, int target) {
        int end=nums.length-1,start=0,mid;
        // 特殊判断
        if (nums[end] < target) 
            return end+1;

        // 程序走到这里一定有 nums[end] >= target,保证插入位置在区间 [start..end]
        while(start<end){
            mid=start+(end-start)/2;
            if(nums[mid]<target)
                start=mid+1;// 下一轮搜索的区间是 [mid + 1..end]
            else
                end=mid;// 下一轮搜索的区间是 [start..mid]
        }
        return end;
    }

      和 二、查找错误的版本类似:在循环中令 end=mid 来保证循环执行结束。


总结

      通用的二分查找判定条件为 start<=end,意味着结果在循环中给出;当采用 start<end 时,结果在循环执行后由 start 或 end 给出。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值