【每日力扣33】第一个错误的版本

一、题目[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 是第一个错误的版本。

示例 2:

输入:n = 1, bad = 1

输出:1

二、思路

二分查找

依题意,即在一个数组中找出特定的值x,因此本题归类于搜索问题。对于一个有序数组,最常用的搜索/查找方法即二分查找法。

对于数组nums[n] = {1, 2, 3, ..., n},先取中间下标值middle = (left + right)/2,判断是否能使调用API返回ture,如果是,则说明第一个错误的版本一定在nums[left, middle]当中;如果否,则说明第一个错误的版本一定在nums[middle, right]当中。不断如此迭代,当最终左右区间下标left == right时,如果调用API返回了true,则说明该下标所对应的版本号nums[left]/nums[right]即为所求的第一个错误版本号。

①递归实现(错误代码):

// The API isBadVersion is defined for you.
// bool isBadVersion(int version);

class Solution {
public:
    int firstBadVersion(int n) {
        vector<int> nums(n);
        for(int i = 0; i < n; i++)
            nums[i] = i+1;//构造数组nums,里面值为版本号{1, 2 ,..., n}
        return nums[BinSearch(nums, 0, n-1)];
    }
    int BinSearch(vector<int>& nums, int left, int right){
        if(left == right)//递归基,当左右区间相等时,
            {
                if(isBadVersion(nums[left]))//如果调用API返回真,
                    return left;//说明该值即为最小错误版本号,返回之
                else//如果API返回假,
                    return -1;//说明所有版本均正确,不存在最小错误版本号,返回-1。
            }
        int middle = (left + right)/2;//取中间下标
        if(isBadVersion(nums[middle]))//判断中间版本号是否为错误版本号
            return BinSearch(nums, left, middle);
        else
            return BinSearch(nums, middle+1, right);
    }
};

时间复杂度为O(logn),空间复杂度为O(n)(数组)+O(logn)(递归调用栈)=O(n)

该代码有两个错误:

(1)构造了一个数组来实现,导致空间复杂度从O(logn)升为O(n),当n很大时,如该test case,n接近INT_MAX时,因为构造一个长度为n的数组导致内存溢出!!

因此我们不应当且没必要去构造数组,可以直接用一组数{1, 2, 3,..., n}来实现。

取消构造数组后,将递归函数的参数列表也去掉vector<int>&,得到如下代码

// The API isBadVersion is defined for you.
// bool isBadVersion(int version);

class Solution {
public:
    int firstBadVersion(int n) {
        return BinSearch(1, n);
    }
    int BinSearch(int left, int right){
        if(left == right)//递归基,当左右区间相等时,
            {
                if(isBadVersion(left))//如果调用API返回真,
                    return left;//说明该值即为最小错误版本号,返回之;
                else//如果API返回假,
                    return -1;//说明所有版本均正确,不存在最小错误版本号,返回-1。
            }
        int middle = (left + right)/2;//取中间下标
        if(isBadVersion(middle))//判断中间版本号是否为错误版本号
            return BinSearch(left, middle);
        else
            return BinSearch(middle+1, right);
    }
};

于是便有了第二个错误

(2)在middle的赋值语句中存在溢出的问题。应将int middle = (left + right)/2改为 int middle = left +(right - left)/2,便可解决溢出问题。

同样为该测试用例,如果n接近于INT_MAX,,同时middle的取值也接近INT_MAX,则在第二轮middle的迭代中,两个接近于INT_MAX的数相加则会导致溢出。

进行优化后,便得到递归实现的正确代码:

 ②迭代实现:

// The API isBadVersion is defined for you.
// bool isBadVersion(int version);

class Solution {
public:
    int firstBadVersion(int n) {
        return BinSearch(1, n);
    }
    int BinSearch(int left, int right){
        while(left < right)//循环条件
        {
            int middle = left + (right - left)/2;//取中间下标
            if(isBadVersion(middle))//判断中间版本号是否为错误版本号
                right = middle;//迭代区间右界限right,继续搜索区间[left, middle]
            else
                left = middle + 1;//迭代区间左界限left,继续搜索区间nums[middle+1, right]
        }//循环退出时,说明已经到达递归方法中的递归基,left == right,现判断left:
        if(isBadVersion(left))//如果调用API返回真,
            return left;//说明该值即为最小错误版本号,返回之;
        else//如果API返回假,
            return -1;//说明所有版本均正确,不存在最小错误版本号,返回-1。
            
    }
};

三、官方题解

(上文与迭代实现类似) 

四、学习心得

①对于可能会有大量规模的数组,谨慎构造!很有可能会有接近于INT_MAX的规模n导致内存溢出!

②进行二分查找时,对于中间值的选取,谨慎将两个区间的值相加!尽量使用left + (right - left)/2 这种方式!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值