二分查找的典型问题(二):二分答案

前篇链接
这里二分答案的意思是:题目要我们找的是一个整数,并且这个整数我们知道它可能的最小值和最大值。此时,我们可以考虑使用二分查找算法找到这个目标值。

根据目标变量具有的单调性质编写判别函数。

在这里插入图片描述

方法一:暴力解法


方法一:暴力解法
我们看示例 2 ,输入 8 返回的是 2,这是因为 3 的平方等于 9 大于 8,因此「结果只保留整数的部分,小数部分将被舍去」。要求我们从 1 开始找,找到最后一个平方以后小于等于 x 的那个数。
我们假设 s 表示从 1 开始的那个数。

  • 如果 s 平方以后小于 x ,暂时放过;
  • 如果 s 平方以后等于 x ,直接返回 s ;
  • 如果 s 平方以后大于 x ,说明 s - 1 是题目要求的,返回 s - 1 。

友情提示:不要忽视对暴力解法的思考,在面试和笔试中可以不实现暴力解法,但是对暴力解法的缺点和潜在的问题需要有一定的分析,过渡到优化解法会更自然。

public class Solution {

    // 暴力解法

    public int mySqrt(int x) {
        // 特判
        if (x <= 1) {
            return x;
        }
        for (int i = 1; i <= x; ++i) {
            if (i == x / i) {
                return i;
            } else if (i > x / i) {
                return i - 1;
            }
        }
        throw new IllegalArgumentException("参数出错");
    }
}

注意:如果判别条件写成 s * s == x ,会发生整型溢出,应该写成 s = x / s ,判别条件 s * s > x 也是类似这样写。
复杂度分析:

  • 时间复杂度:O(x),最坏情况下暴力解法会一直尝试到 x/2,O(x/2) = O(x);
  • 空间复杂度:O(1)O(1),只使用到常数个变量。

方法二:二分查找


通过对暴力解法的分析,我们知道了,需要返回最后一个平方以后小于等于 x 的数。使用二分查找的思路 2,关键在于分析那些数是我们不要的。

很容易知道,如果一个数的平方大于 x ,这个数就一定不是我们要找的平方根。于是,可以通过逼近的方式找到平方根。
错误代码

class Solution {
    public int mySqrt(int x) {
        int left = 0;
        int right = x;
        
        while (left < right){
            // 写完分支以后调整为向上取整
            int mid = (left + right + 1) / 2;
            if (mid * mid > x) {
                // mid 以及大于 mid 的数一定不是解,下一轮搜索的区间为 [left, mid - 1]
                right = mid - 1;
            } else {
                left = mid;
            }
        }
        return left;
    }
}

提交以后,系统提示:
在这里插入图片描述
我们看到是这个很大的数,根据暴力解法的经验可以断定,是在计算 mid * mid 的时候出错了, mid * mid 超出了整数能表达的最大值。为此可以做如下修改:mid > x / mid

class Solution {

    public int mySqrt(int x) {
        int left = 0;
        int right = x;
        
        while (left < right){
            // 写完分支以后调整为向上取整
            int mid = (left + right + 1) / 2;
            if (mid > x / mid) {
                // mid 以及大于 mid 的数一定不是解,下一轮搜索的区间为 [left, mid - 1]
                right = mid - 1;
            } else {
                left = mid;
            }
        }
        return left;
    }
}

但事实上,只要仔细想一想就知道,一开始右边界 int right = x; 没有必要设置这么大(他根本不会取到这个值)。只要设置成它的一半即可。但是有个数 0 例外,单独对它做判断即可。下面这样写,也把 1 的平方根是 1包括进去了。

class Solution {

    public int mySqrt(int x) {
        if (x == 0) {
            return 0;
        }
        int left = 1;
        int right = x / 2;
        
        while (left < right){
            // 写完分支以后调整为向上取整
            int mid = (left + right + 1) / 2;
            
            if (mid > x / mid) {
                // mid 以及大于 mid 的数一定不是解,下一轮搜索的区间为 [left, mid - 1]
                right = mid - 1;
            } else {
                left = mid;
            }
        }
        return left;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值