You are a product manager and currentlyleading a team to develop a new product. Unfortunately, the latest version ofyour product fails the quality check. Since each version is developed based onthe previous version, all the versions after a bad version are also bad.
Suppose you have n versions [1, 2, ..., n]and you want to find out the first bad one, which causes all the following onesto be bad.
You are given an API bool isBadVersion(version) which will return whether version is bad. Implement afunction to find the first bad version. You should minimize the number of callsto the API.
Credits:
Special thanks to @jianchao.li.fighter foradding this problem and creating all test cases.
题目大意是找到第一个不符合要求的版本号,因为版本号有序,因为题目要求最少次数调用 isBadVersion(version)函数,所以很容易想到二分查找。但是这题有个陷阱,这也是为什么写这篇博客的原因。大家熟悉的二分查找,在求中间位置的时候,都是 mid = (start + end)/2; 但这是有问题的,假设mid,start,end全为int型,当数组长度较大(即end可能会很大),而要查找的值的位置也比较接近end,或者要找的数就是最后一个数。此时 start 也会变得很大,这样mid很有可能超过了int的表示范围。解决这个问题很简单,但是忽略这个问题也特别简单。其实这个问题可以避免,就是我们换一种方式求mid,即 mid = start + (end - start)/ 2 。我们来分析一下这个式子,因为mid,start,end全是int型,所以end-start肯定是int型,而(end-start)/2当start和end比较接近时结果较小,此时start可能会很大,但因为end没有超过int型的表示范围,则start+(end - start)/2也不会超过。同样的道理,当end和start离的很远时也不会超过。这就是比较安全的求mid的方法。
还有一种不太推荐的方法可以防止start+end的和溢出,就是我们可以扩大类型,把mid,start,end全换成long long int即可。下面给出这两个方法的代码,一个非递归,一个递归。
方法一:
bool isBadVersion(int version);
class Solution {
public:
int firstBadVersion(int n) {
int s = 1;
int e = n;
while (s <= e)//注意这个等号
{
int mid = s + (e - s)/2;
if (isBadVersion(mid))
{
e = mid - 1;
}
else
s = mid + 1;
}
return s;
}
};
方法二:
bool isBadVersion(int version);
class Solution {
public:
void helper(long long int s,long long int e,long long int * ret)
{
if (s > e)
{
return ;
}
long long int t = s + e;
long long int mid = t / 2;
if (isBadVersion(mid))
{
*ret = mid;
helper(s,mid-1,ret);
}
else
helper(mid+1,e,ret);
}
int firstBadVersion(int n) {
int s = 1;
int e = n;
long long int ret;
long long int * p = &ret;
helper(s,e,p);
return ret;
}
};