leetcode:我能赢吗

题目来源:力扣

题目介绍:

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

审题:

初看该题目以为是一道普通的博弈问题, 尝试使用动态规划算法去解决. 但一分析发现遇到了麻烦. 当尝试使用动态规划算法时,我们考虑两个状态变量, 一是当前累加和, 另一个是当前可选整数. 第一个状态变量的取值规模为O(N)O(N), 而另一个状态变量的取值规模为多少呢?事实上, 我们很难给出确切的范围, 因为这涉及到前几步选择的哪些整数. 不过我们知道, 所有可能的取值集合的规模大小为O(2M)O(2^M), M为可选最大整数.如果这样去解决问题, 我们的子问题规模为O(N2M)O(N2^M), 而每一个子问题的选择集规模为O(M)O(M), 因此该算法的时间复杂度为O(MN2M)O(MN2^M). 这么大的时间复杂度, 要想应用的实际中, 是不切实际的.

我们前面提到, 造成上述动态规划算法时间复杂度如此庞大的主要原因在于我们很难给出状态变量当前可选整数集合的确切范围, 而不得不分析了所有的2M2^M种可能的状态, 而事实上其中绝大部分状态是不可能在双人博弈过程中出现的.

现在我们不得不回到另一中思路, 记忆化搜索. 我们使用自顶向下的递归方法, 通过自顶向下的搜索, 我们仅会搜索哪些必要的状态.因而时间复杂度能够大幅下降.

该题目的另一个要点在于使用位向量表示集合. 而位向量进一步可以使用一个大整数表示. 由于题目中最大可选整数不会超过20, 因此我们可以使用64位整数表示位向量.

import java.util.AbstractMap.SimpleEntry;

class Solution {
    Map<SimpleEntry<Integer, Integer>, Boolean> map = new HashMap<>();

    private boolean dfs(int used, int desiredTotal, int maxChoosableInteger){
        SimpleEntry<Integer, Integer> key = new SimpleEntry<>(used, desiredTotal);
        if(map.containsKey(key))
            return map.get(key);
        
        for(int k = 1; k <= maxChoosableInteger; k++){
            if(((1 << (k-1)) & used) == 0){
                if(k >= desiredTotal){
                    map.put(key, true);
                    return true;
                }
                else if(!dfs(used | 1 << (k-1), desiredTotal-k, maxChoosableInteger)){
                    map.put(key, true);
                    return true;
                }
            }
        }
        map.put(key, false);
        return false;
    }

    public boolean canIWin(int maxChoosableInteger, int desiredTotal) {
        int sum = 0;
        for(int i = 1; i <= maxChoosableInteger; i++)
            sum += i;
        if(sum < desiredTotal)
            return false;
        return dfs(0, desiredTotal, maxChoosableInteger);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章