464. 我能赢吗
题目:
在 "100 game" 这个游戏中,两名玩家轮流选择从 1 到 10 的任意整数,累计整数和,先使得累计整数和达到 100 的玩家,即为胜者。
如果我们将游戏规则改为 “玩家不能重复使用整数” 呢?
例如,两个玩家可以轮流从公共整数池中抽取从 1 到 15 的整数(不放回),直到累计整数和 >= 100。
给定一个整数 maxChoosableInteger (整数池中可选择的最大数)和另一个整数 desiredTotal(累计和),判断先出手的玩家是否能稳赢(假设两位玩家游戏时都表现最佳)?
你可以假设 maxChoosableInteger 不会大于 20, desiredTotal 不会大于 300。
示例:
输入:
maxChoosableInteger = 10
desiredTotal = 11
输出:
false
解释:
无论第一个玩家选择哪个整数,他都会失败。
第一个玩家可以选择从 1 到 10 的整数。
如果第一个玩家选择 1,那么第二个玩家只能选择从 2 到 10 的整数。
第二个玩家可以通过选择整数 10(那么累积和为 11 >= desiredTotal),从而取得胜利.
同样地,第一个玩家选择任意其他整数,第二个玩家都会赢。
解答:
不太好用自底向上,因为状态并不全部需要,比如我这一把直接稳赢(有一个未使用数字比目标值大,取了就赢),那当前状态的所有子状态就不再用计算。
用一个vector<bool>memo来记录状态,长度为maxChoosableInteger+1。memo[i]=true:该数字取过,memo[i]=false:该数字没取过。
然后用一个map来记录每个算过的状态到底能不能稳赢,这样再算的时候如果出现重复的状态就可以直接查表返回。
也就是自顶向下记忆化搜索:
class Solution { public: unordered_map<vector<bool>,bool> memo; bool canIWin(int maxChoosableInteger, int desiredTotal) { int p=(maxChoosableInteger+1)*maxChoosableInteger/2; if(p<desiredTotal){ return false; } vector<bool> used(maxChoosableInteger+1,false); canwin(maxChoosableInteger,desiredTotal,used); return memo[used]; } bool canwin(int maxChoosableInteger,int desiredTotal,vector<bool>& used){ if(memo.count(used)){ return memo[used]; } for(int i=used.size()-1;i>=1;--i){ if(used[i]==false){ if(i>=desiredTotal){ memo[used]=true; return true; } break; } } for(int i=1;i<=maxChoosableInteger;++i){ if(used[i]==true){continue;} used[i]=true; if(canwin(maxChoosableInteger,desiredTotal-i,used)==false){ used[i]=false; memo[used]=true; return true; } used[i]=false; } memo[used]=false; return false; } };
优化方法:
由于题目说了maxChoosableInteger不超过20,那么一个int有32位,用二进制位为1或0来指示数字是否存在,我们只需要int的后maxChoosableInteger位即可。
class Solution { public: unordered_map<int,bool> memo; bool canIWin(int maxChoosableInteger, int desiredTotal) { //如果1~maxChoosableInteger加起来都不够用,返回false int p=(maxChoosableInteger+1)*maxChoosableInteger/2; if(p<desiredTotal){ return false; } canwin(maxChoosableInteger,desiredTotal,0); return memo[0]; } //flag起记录作用,flag最后一位指代maxChoosableInteger是否使用过,倒数第二位指代maxChoosableInteger-1是否使用过,以此类推。。 bool canwin(int maxChoosableInteger,int desiredTotal,int flag){ if(memo.count(flag)){ return memo[flag]; } int p=1; //取最大的数,如果超过desiredTotal,那么稳赢 //大到小尝试 for(int i=maxChoosableInteger;i>=1;--i){ if((flag&p) == 0){//i还没用过 if(i>=desiredTotal){ memo[flag]=true; return true; } break; } p<<=1; } //没法当前轮次稳赢 //从剩余的最小数字开始尝试 p=1; for(int i=1;i<maxChoosableInteger;++i){ p<<=1; } //小到大尝试 for(int i=1;i<=maxChoosableInteger;++i){ if(flag&p){ p>>=1; continue; } if(canwin(maxChoosableInteger,desiredTotal-i,flag|p)==false){//对手无法赢,那么我们可以这轮赢 memo[flag]=true; return true; } p>>=1; } memo[flag]=false; return false; } };