LeetCode算法题解——二分查找2

文章介绍了LeetCode中四道与二分查找相关的算法题,包括寻找左边界(第一个错误的版本)、右边界(寻找比目标字母大的最小字母、有效的完全平方数)以及找一个数(x的平方根)。通过解题思路和代码实现,阐述了如何利用二分查找优化搜索过程,特别是处理边界情况的技巧。
摘要由CSDN通过智能技术生成

接上文LeetCode算法题解——二分查找1,本篇总结LeetCode中部分题目的二分查找解法。

第四题(寻找左边界)

278. 第一个错误的版本

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

假设你有 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 是第一个错误的版本。

思路

如果将n个版本的true和false放到一个数组里,应该是[false, false, …, true, true],前面都是false,后面都是true。这道题可以看作是找到最后一个false,也可以看作是找到第一个true。在这里,我将它当作找到第一个true来做,也就是寻找true的左边界。

代码

java版本

public class Solution extends VersionControl {
    public int firstBadVersion(int n) {
        // 左闭右闭
        int l = 1;
        int r = n;
        // 寻找true的左边界
        while(l <= r){
            // 防止溢出
            int m = l + (r - l) / 2;
            // 能找到,缩小右边界
            if(isBadVersion(m) == true){
                r = m - 1;
            }
            // 找不到,缩小左边界
            else if(isBadVersion(m) == false){
                l = m + 1;
            }
        }
        return l;
    }
}

python版本

class Solution:
    def firstBadVersion(self, n: int) -> int:
        l, r = 1, n
        while l <= r:
            m = l + (r - l) // 2
            if isBadVersion(m):
                r = m - 1
            else:
                l = m + 1
        return l

总结

在寻找左边界的时候,和之前中点值找到target一样,找到就收缩右边界。拓展一下,在寻找左/右边界,找到了target,但还没有到达其边界,应该向你要搜索的边界靠近,进一步压缩搜索空间。

第五题(寻找右边界)

744. 寻找比目标字母大的最小字母

题目描述:
给你一个字符数组 letters,该数组按非递减顺序排序,以及一个字符 target。letters 里至少有两个不同的字符。

返回 letters 中大于 target 的最小的字符。如果不存在这样的字符,则返回 letters 的第一个字符。

示例1:

输入: letters = ["c", "f", "j"],target = "a"
输出: "c"
解释:letters 中字典上比 'a' 大的最小字符是 'c'。

思路

按照题意,寻找第一个比target大的字母。在前面寻找右边界时我们已经提到,当循环停止时右边界r以右的元素都大于target,则右边界r以左的元素都小于等于target,右边界r即最后一个target。所以,第一个比target大的字母就是r+1处的字母。但是当target比数组中所有数字都要大的时候,r+1会越界,这里需要做特别处理。

代码

java版本

class Solution {
    public char nextGreatestLetter(char[] letters, char target) {
        // 左闭右闭
        int l = 0;
        int r = letters.length - 1;
        // 寻找右边界
        while(l <= r){
            // 防止溢出
            int m = l + (r - l) / 2 ;
            // 收缩左边界
            if(target >= letters[m]){
                l = m + 1;
            }
            // 收缩右边界
            else{
                r = m - 1;
            }
        }
        // 防止越界,当不存在这样的字符,即target最大
        if(r == letters.length - 1){
            return letters[0];
        }
        return letters[r + 1];
    }
}

python版本

class Solution:
    def nextGreatestLetter(self, letters: List[str], target: str) -> str:
        l, r = 0, len(letters) - 1
        while l <= r:
            m = l + (r - l) // 2
            if target >= letters[m]:
                l = m + 1
            else:
                r = m - 1
        return letters[(r + 1) % len(letters)]

总结

这道题,需要将大于 target 的最小的字符理解成第一个比target大的字符,并且知道可以通过寻找target的右边界来求解。

第六题(找一个数)

367. 有效的完全平方数

题目描述:
给你一个正整数 num 。如果 num 是一个完全平方数,则返回 true ,否则返回 false 。

完全平方数 是一个可以写成某个整数的平方的整数。换句话说,它可以写成某个整数和自身的乘积。

不能使用任何内置的库函数,如 sqrt 。

示例1:

输入:num = 16
输出:true
解释:返回 true ,因为 4 * 4 = 16 且 4 是一个整数。

思路

首先思考一种暴力算法。以num=16为例,我们先从1开始穷举。1*1=1,1<16,不满足。2*2=4,4<16,不满足。3*3=9,9<16,不满足。4*4=16,16==16,满足!5*5=25,25>16,不满足。显然,比4小的数和比4大的数,都不满足,我们可以通过二分法筛选掉比4小的数和比4大的数。

值得注意的是,如果直接比较x*x和num,可能x*x会超过int的范围。可以转化为比较x和num/x。如果是这样的话,考虑到整数除法结果仍然为整数,当x=num/x时,num可能是x*x,x*x+1,…,x*x+(x-1)。此时只需要再次比较x*x和num,即可。

代码

java版本

class Solution {
    public boolean isPerfectSquare(int num) {
        // 左闭右闭
        int l = 1;
        int r = num;
        while(l <= r){
            // 防止溢出
            int m = l + (r - l) / 2;
            int sqrt = num / m;
            // 找到target并进行判断
            if((m == sqrt) && (m * sqrt == num)){
                return true;
            }
            else if(m < sqrt){
                l = m + 1;
            }
            else{
                r = m - 1;
            }
        }
        return false;
    }
}

python版本

class Solution:
    def isPerfectSquare(self, num: int) -> bool:        
        l, r = 1, num
        while l <= r:
            m = l + (r - l) // 2
            sqrt = num // m
            if sqrt < m:
                r = m - 1
            else:
                l = m + 1
        
        return r * r == num

总结

这道题,我们可以先思考一个穷举的办法,然后考虑可以用二分法进行优化。防止溢出是一个需要注意的点。这道题里,m是中点,sqrt是target。和以前不同,这道题的target是会变的。

第七题(找右边界)

69. x 的平方根

题目描述:
给你一个非负整数 x ,计算并返回 x 的 算术平方根 。

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

注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。

示例1:

输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。

思路

这题和上一题不同。在上一题中,完全平方数,如16,一定能找到整数4*4=16。但是在这题里,将平方根的小数部分舍去了,那么就不能通过x*x=num来进行判断。同样的,我们先思考一种暴力算法。以num=8为例,我们先从1开始穷举。1*1=1,1<8;2*2=4,4<8;3*3=9,9>8。显然,前面的平方数都比8小,后面的平方数都比8大,我们需要找到最后一个比8小的平方数。问题转化为了找比8小的平方数的右边界。

和上一题一样,如果直接比较x*x和num,可能x*x会超过int的范围,可以转化为比较x和num/x。

代码

java版本

class Solution {
    public int mySqrt(int x) {
        if(x <= 1){
            return x;
        }
        // 左闭右闭
        int l = 1;
        int r = x;
        while(l <= r){
            // 防止溢出
            int m = l + (r - l) / 2;
            int sqrt = x / m;
            if(m > sqrt){
                r = m - 1;
            }
            else{
                l = m + 1;
            }
        }
        return r;
    }
}

python版本

class Solution:
    def mySqrt(self, x: int) -> int:
        if x == 0:
            return x

        l, r = 1, x
        while l <= r:
            m = l + (r - l) // 2
            sqrt = x // m
            if sqrt < m:
                r = m - 1
            else:
                l = m + 1
        return r

总结

同样的,这道题我们先思考一个穷举的办法,然后考虑可以用二分法进行优化。防止溢出依旧是一个需要注意的点。这道题里,m是中点,sqrt是target,会发生改变。

下一篇博客LeetCode算法题解——二分查找3中,我将分享LeetCode中几道比较难想到使用二分查找解法的题目。

参考:

二分查找算法细节详解,顺便写了首诗

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值