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