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);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章