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;
    }
};

 

posted @ 2020-03-17 15:52  NeoZy  阅读(257)  评论(0编辑  收藏  举报