为什么二分法不建议使用 (right + left)除2?

为什么二分法不建议使用 (right + left)除2?

每天一个小知识,不定期更新


一、问题

事情是这样的,由于我最近在刷题,刷到这样一道题:

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

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

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


示例:
给定 n = 5,并且 version = 4 是第一个错误的版本。

调用 isBadVersion(3) -> false
调用 isBadVersion(5) -> true
调用 isBadVersion(4) -> true

所以,4 是第一个错误的版本。 

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/first-bad-version

乍一看,题目是挺简单的。 直接采用二分法就可以立马解决。 于是我潇潇洒洒的写下

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

也许,你看到我的代码,你会想没错啊!!!

自己也会这样写。 是的在我之前所有的二分法都是这样写的。

当我满怀信心的点了提交,然而

运行失败:
Time Limit Exceeded
测试用例:2126753390
1702766719

?????????Why

二、分析

须知:Integer都有自己的表示范围,他有定义MAX_VALUEMIN_VALUE

/**
 * A constant holding the minimum value an {@code int} can
 * have, -2<sup>31</sup>.
 */
@Native public static final int   MIN_VALUE = 0x80000000;

/**
 * A constant holding the maximum value an {@code int} can
 * have, 2<sup>31</sup>-1.
 */
@Native public static final int   MAX_VALUE = 0x7fffffff;

,当我们的数超过他所规定的最值时,Integer将无法表示。

因此在我们上述算法中,

  1. left <= MAX_VALUE和 right <= MAX_VALUE是肯定的
  2. 但是left+right <= MAX_INT 我们无法确定,所以会造成栈溢出。

那么知道问题所在,我们怎么修改我们的代码呢?

我们可以使用

int mid = left + (right - left) /2;

来代替

int mid = (right + left) / 2;

他们最后的结果都是一致的,却能够有效的避免栈溢出问题。

三、结论

所以在我们使用二分法时,我们可以使用left + (right - left) /2来代替(right + left) / 2,来避免栈溢出。

参考: 1、为什么left +(right-left)/ 2不会溢出? 🔗 2、第一个错误的版本 🔗

lomtom

标题:为什么二分法不建议使用 (right + left)除2?

作者:lomtom

链接:https://lomtom.cn/每天一个小知识为什么二分法不建议使用-right--left除2