题目:
示例 :
输入:maxChoosableInteger = 10, desiredTotal = 11 输出:false 解释: 累计和为俩选手共同的和; 无论第一个玩家选择哪个整数,他都会失败。 第一个玩家可以选择从 1 到 10 的整数。 如果第一个玩家选择 1,那么第二个玩家只能选择从 2 到 10 的整数。 第二个玩家可以通过选择整数 10(那么累积和为 11 >= desiredTotal),从而取得胜利. 同样地,第一个玩家选择任意其他整数,第二个玩家都会赢。(不管第一个玩家选择哪个数字都不会 达到目标数,而不管 第一个玩家选择哪个数字,第二个玩家总能在剩下的数字中找到一个数字使总和 达到目标数)
思路:
这里先假设一定有一个选手获胜,一个选手失败!
我们可以定义目前的总和数为sum来判断是否获胜;总和指共同总和而不是各自总和。
我们可以从中发现,当当前的总和数sum加上当前选手选择的数后,若其值大于或等于目标值(desiredTotal)则获得胜利;
即便当前总和数sum无法达到目标值,但随着俩位选手的最优选择后,sum会逐渐增大直到达到目标值,如此我们便可以采用递归:
每一层都在上一层的基础上加上自己的最优解再进行判断
由于我们是要考虑先手是否获胜,所以可以采用
1.当前总和加当前选择值是否大于目标值;
2.对手是否必定失败
这里有一点需要注意的是,什么是最优解?由于找不到规律所以我们只能遍历所有解而且在遍历中不返回false,只返回true,除非所有值都被遍历任未返回true值,我们才能返回false值;
大致代码如下:
for(int i=1;i<=maxChoosableInteger;i++){//遍历以获得最优解
if(sum+i>=desiredTotal){//当前总和达到目标值
return true;
}
if(!dfs(sum+i,maxChoosableInteger,desiredTotal)){//如果对手必输即对手返回false
return true;
}
}
return false;
接下来我们要注意的是,题目要求值不放回,也就是每个值只能被拿一次;那么我们可以定义一个二进制数state来存储对应值是否被拿,如二进制的10可以表示1已经被使用,二进制的1000可以表示3已经被使用,二进制的1010可以表示1和3已经被使用,在循环中如果遇到已经使用过的值那么我们直接continue
在递归或循环之中我们总会遇到已经判断过的情况,而不同的情况是由state和当前总和sum决定,而当前总和也可以由state得到,故我们可以定义一个数组其下标为state的不同值来实现记忆化搜索
前面我们讲到假设必定有一人胜利,一人失败,那么接下来我们就得考虑其它情况:
如果俩人将所有值都拿了以后仍然无法达到目标值,那么依照题意我们直接返回false
我们也不难发现,如何目标值小于可以选择的最大整数,那么先手必定获胜直接返回true。
下面是完整代码: