循序漸進,得到動態規劃解法

486. 預測贏家

給定一個表示分數的非負整數數組。 玩家1從數組任意一端拿取一個分數,隨後玩家2繼續從剩餘數組任意一端拿取分數,然後玩家1拿,……。每次一個玩家只能拿取一個分數,分數被拿取之後不再可取。直到沒有剩餘分數可取時遊戲結束。最終獲得分數總和最多的玩家獲勝。

給定一個表示分數的數組,預測玩家1是否會成爲贏家。你可以假設每個玩家的玩法都會使他的分數最大化。

示例

輸入: [1, 5, 233, 7]
輸出: True
解釋: 玩家1一開始選擇1。然後玩家2必須從5和7中進行選擇。無論玩家2選擇了哪個,玩家1都可以選擇233。
最終,玩家1(234分)比玩家2(12分)獲得更多的分數,所以返回 True,表示玩家1可以成爲贏家。
注意:

1 <= 給定的數組長度 <= 20.
數組裏所有分數都爲非負數且不會大於10000000。
如果最終兩個玩家的分數相等,那麼玩家1仍爲贏家。

解題思路

本題的思考過程就展現在代碼中了。注意這類博弈問題(也就是minmax問題)如果直接返回本輪先手-後手差值,那麼就不必糾結啥時候取min啥時候取max了。

p.s. 本題動態規劃解法部分dp[i][j]表示的是nums[i…j]序列所對應的那一輪的先後手的先手-後手差值的最大值。這裏有一個很神奇的結論:任何一個序列,對應的該輪的先後手是唯一確定的。

代碼

// 版本1:遞歸版本 296ms
class Solution {
public:
    bool PredictTheWinner(vector<int>& nums) {
        int len = nums.size();
        if(len & 1 == 0)    return true;
        
        if(GetScoreDiff(nums, 0, len - 1) >= 0)   return true;
        else return false;
    }
    
    // 獲得(每一輪)先手-後手的分數差的最大值
    int GetScoreDiff(vector<int>& nums, int l, int r) {
        int len = r - l + 1;
        if(len <= 1) return nums[l];    // 數組只剩下一個元素了
        int a = nums[l], b = nums[r];
        return max(a - GetScoreDiff(nums, l + 1, r), b - GetScoreDiff(nums, l, r - 1));
    }
};

// 版本2:遞歸 + 記憶化 8ms
class Solution {
private:
    int** dp;
public:
    bool PredictTheWinner(vector<int>& nums) {
        int len = nums.size();
        if(len & 1 == 0)    return true;
        
        dp = new int*[len];
        for(int i = 0; i < len; i++)
            dp[i] = new int[len];
        
        for(int i = 0; i < len; i++)
            for(int j = 0; j < len; j++)
                dp[i][j] = 0x3f3f3f3f;
        
        if(GetScoreDiff(nums, 0, len - 1) >= 0)   return true;
        else return false;
        
    }
    
    // 獲得(每一輪)先手-後手的分數差的最大值
    int GetScoreDiff(vector<int>& nums, int l, int r) {
        int len = r - l + 1;
        if(len <= 1) return nums[l];    // 數組只剩下一個元素了
        if(dp[l][r] != 0x3f3f3f3f)  return dp[l][r];
        int a = nums[l], b = nums[r];
        dp[l][r] = max(a - GetScoreDiff(nums, l + 1, r), b - GetScoreDiff(nums, l, r - 1));
        return dp[l][r];
    }
};


// 版本3:遞推(消除尾遞歸) 4ms
class Solution {
public:
    bool PredictTheWinner(vector<int>& nums) {
        int len = nums.size();
        if(len & 1 == 0)    return true;
        
        if(GetScoreDiff(nums) >= 0)   return true;
        else return false;
    }
    
    // 獲得(每一輪)先手-後手的分數差的最大值
    int GetScoreDiff(vector<int>& nums) {
        int len = nums.size();
        if(len <= 1) return nums[0];    // 數組只有一個元素
        vector<vector<int>> dp(len, vector<int>(len));
        for(int i = len - 1; i >= 0; i--) {     // i = len - 1時根本不會執行內層循環,不會越界
            dp[i][i] = nums[i];
            for(int j = i + 1; j < len; j++) {
                dp[i][j] = max(nums[i] - dp[i + 1][j], nums[j] - dp[i][j - 1]);
            }
        }
        return dp[0][len - 1];
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章