Leetcode464. 我能赢吗

Leetcode464. 我能赢吗

博弈问题
递归+备忘录+压缩状态

题目

在 “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),从而取得胜利.
同样地,第一个玩家选择任意其他整数,第二个玩家都会赢。

法1:递归法

  • 这是一道博弈类问题:“稳赢”指的是,假设有两个人A, B,A先抽整数然后轮到B,再轮到A…,每次都根据要凑的和选择最有利于自己赢的数。
    首先要理解什么是稳赢:在AB都各自执行最优策略达到某个状态时,A存在一种走法,在走完后,无论B怎么走,A都能取胜,因此我们需要不断地递归,遍历A走一步后的各种情况,从而判断A能否获胜。
  • 传统的做法可能想标记A和B,即表示当前状态下,该轮到谁进行操作。但实际上并不需要进行标记,其实这就是这类问题的关键: 因为是两个人在博弈, 所以从当前状态转移到下一个状态时, 就体现了动作执行这的变化。如说当前状态是A, 因为是两个人在玩, 下一个状态就是B. 这里很关键, 希望大家好好思考一下。
  • 递归函数dfs的含义是,某人(与AB无关)先手操作到达当前状态,能否稳赢。
class Solution {
    boolean[] visited; //用来标记某个数是否被使用过
    int maxChoosableInteger, desiredTotal;
    // 当前这个状态下, 能否稳赢.
    bool dfs(int total_sum) {
        // 递归的边界
        if (total_sum >= desiredTotal) return true; //这种情况是稳赢的
        //递归遍历各种情况
        for (int i = 1; i <= maxChoosableInteger; ++i) {
            if (visited[i]) continue;
            vis[i] = true;
            if (dfs(total_sum + i)) {
                visited[i] = false;
                return false; // 如果在当前状况下,对手通过取i能够获得胜利,说明自己无法稳赢,返回false
            }
            visited[i] = false; //当前这轮循环完成,换源i标记
        }
        //当所有情况遍历完,dfs都是false的时候,可以退出循环,表示对手无论取哪个数都无法胜利,这说明自己可以稳赢,返回true。
        return true;
    }
    bool canIWin(int maxChoosableInteger_, int desiredTotal_) {
    	visited = new int[maxChoosableInteger+1];
        maxChoosableInteger = maxChoosableInteger_;
        desiredTotal = desiredTotal_;
        
        for (int i = 1; i <= maxChoosableInteger; ++i) {
            visited[i] = true; // A先手,从第i个数开始
            if (dfs(i)) return true; //表示判断在A先手达到状态i的情况下,能否稳赢。
        }
        return false;
    }
};

法二:状态压缩+递归+记忆化(动态规划)

在递归法中,进行了大量的重复计算,比如[a选3,b选2]、[a选2,b选3],因此我们需要对状态进行记忆,这里就用到了状态压缩
状态压缩指的是用位来表示所有的情况,在该题目中,如果maxChooseableInteger = 4,那么我们可以使用“0000”四位表示状态,“0100”表示3已被选,“0101”表示3和1已被选。

class Solution {
    public boolean canIWin(int maxChoosableInteger, int desiredTotal) {
        if(maxChoosableInteger > 20 || maxChoosableInteger < 0 || desiredTotal > 300 || desiredTotal <0){
            return false;
        }
        if((1+maxChoosableInteger)*maxChoosableInteger/2 < desiredTotal){
            return false;
        }
        Boolean[] dp = new Boolean[1<<maxChoosableInteger]; //要用Boolean初始化,这样每个元素才能为null
        return backtrack(maxChoosableInteger, desiredTotal, dp, 0);
    }

// 表示从状态state开始,先手能否获胜。
    public boolean backtrack(int maxChoosableInteger, int desiredTotal, Boolean[] dp, int state){
        if(dp[state] != null){ //无需重复计算
            return dp[state];
        }
        for(int i = 1; i <= maxChoosableInteger; i++){
            if((state & (1 << (i-1))) == 0){ //若第i个数未被使用过,查看选i能否获胜。
                if(desiredTotal - i <= 0 || backtrack(maxChoosableInteger, desiredTotal-i, dp, state | (1<<(i-1))) == false){ //当选i大于目标值,或者接下来对手无法获胜时,该状态先手能够稳赢。
                    dp[state] = true;
                    return true;
                }
            }
        }
        //否则,不能稳赢
        dp[state] = false;
        return false;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值