题目如下:
We are playing the Guess Game. The game is as follows:
I pick a number from 1 to n. You have to guess which number I picked.
Every time you guess wrong, I'll tell you whether the number is higher or lower.
You call a pre-defined API guess(int num)
which returns 3 possible results (-1
,1
, or0
):
-1 : My number is lower
1 : My number is higher
0 : Congrats! You got it!
Example:
n = 10, I pick 6.
Return 6.
题目当然还是一如既往地好理解,就是一个猜数字的游戏。你猜一个数字,内置的guess(int num)函数告诉你要猜的数字比你猜的数字的值低了还是高了。例如:目标数字是6,guess(10)会返回-1,因为6<10。如果正好猜对了就返回0。我们要写的程序就是要把这个目标数字猜出来,过程就是通过不断调整n的大小,然后使用guess(n)函数来判断是大了还是小了,通过不断的尝试最后得到正确的值。最容易想到的方法是如果guess(n)返回-1我就让n=n-1,如果guess(n)等于1我就让n=n+1,这样最后肯定能得到正确的结果。确实能得到正确的结果,但是你还会得到一个大大的“超时”提示。说明这种最容易想到的方法是行不通的,那我们就很容易想到一种需要运行次数更好的方法,二分法。确定一个比目标值大的右边界,再确定一个比目标值小的左边界,不断取中间值进行比较,然后确定新边界,最终一定会找到和目标值相等的n。思路就是这样,已Accepted的代码如下所示,上面有我加的一些注释方便理解:(题干中的目标值是从1到n中选择的,所以并不会出现目标值比n大的情况,我这种方法包含了目标值比n大的情况,这里其实还有一些小隐患,n*2是有可能超出int类型最大值的,所以在赋值之前要弄一个long类型的进行判断,要是n*2大于int类型最大值,就令n赋值为int类型最大值,若不大于正常进行赋值即可)
public int guessNumber(int n) {
int left = 0;
int right = 0;
int flag = 0;
while(true){
flag = guess(n);
if(flag == -1){ // target number < n
right = n;
n = left+(right-left)/2;
}else if(flag == 1){ // target number > n
if(right>0){ //if right boundary has existed
left = n;
n = left+(right-left)/2;
}else{ //if right boundary has not existed, double 'n' to make it bigger than guessnumber
n = n*2;
}
}else{
return n;
}
}
}
稍微修改了一下,这样n*2就不会溢出了,代码如下所示:
public int guessNumber(int n) {
int left = 0;
int right = 0;
int flag = 0;
while(true){
flag = guess(n);
if(flag == -1){ // target number < n
right = n;
n = left+(right-left)/2;
}else if(flag == 1){ // target number > n
if(right>0){ //if right boundary has existed
left = n;
n = left+(right-left)/2;
}else{ //if right boundary has not existed, double 'n' to make it bigger than guessnumber
long test = n;
test = test*2;
if(test > 2147483647L){
n = 2147483647;
}else{
n = n*2;
}
}
}else{
return n;
}
}
}
这道题是有Editoral Solution的,我们现在就来看一下,Editoral Solution中的其他解法。
第一种方法就和我提到的方法类似,他的方法是从1开始遍历,最终总能遇见目标值,显然这种方法会超时,代码如下所示:
public class Solution extends GuessGame {
public int guessNumber(int n) {
for (int i = 1; i < n; i++)
if (guess(i) == 0)
return i;
return n;
}
}
第二种方法也是利用了二分搜索,和我的方法类似,这里就直接放代码了:
public int guessNumber(int n) {
int low = 1;
int high = n;
while (low <= high) {
int mid = low + (high - low) / 2;
int res = guess(mid);
if (res == 0)
return mid;
else if (res < 0)
high = mid - 1;
else
low = mid + 1;
}
return -1;
}
第三种方法是使用三分搜索,解释的话大致直接看英文原文的解释好了,链接在这里:https://leetcode.com/articles/guess-number-higher-or-lower/ 。代码如下所示:
public int guessNumber(int n) {
int low = 1;
int high = n;
while (low <= high) {
int mid1 = low + (high - low) / 3;
int mid2 = high - (high - low) / 3;
int res1 = guess(mid1);
int res2 = guess(mid2);
if (res1 == 0)
return mid1;
if (res2 == 0)
return mid2;
else if (res1 < 0)
high = mid1 - 1;
else if (res2 > 0)
low = mid2 + 1;
else {
low = mid1 + 1;
high = mid2 - 1;
}
}
return -1;
}
大致就是这几种方法了,三分的时间复杂度比二分小,这个可以用主定理解释,主定理可以计算分治问题的时间复杂度,详细可以看这里:http://blog.chinaunix.net/uid-25267728-id-3802135.html 主要看那张图片就行,那个就是从算法导论上截出来的~