[LeetCode] 1140. Stone Game II 石子游戲之二
Alice and Bob continue theirgames with piles of stones. There are a number ofpilesarranged in a row, and each pile has a positive integer number of stonespiles[i]
. The objective of the game is to end with the moststones.
Aliceand Bob take turns, with Alice starting first. Initially,M = 1
.
On each player's turn, that playercan takeall the stonesin thefirstX
1 <= X <= 2M
. Then, we setM = max(M, X)
.
The game continues until all the stones have been taken.
Assuming Alice and Bob play optimally, return the maximum number of stones Alicecan get.
Example 1:
Input: piles = [2,7,9,4,4] Output: 10 Explanation: If Alice takes one pile at the beginning, Bob takes two piles, then Alice takes 2 piles again. Alice can get 2 + 4 + 4 = 10 piles in total. If Alice takes two piles at the beginning, then Bob can take all three piles left. In this case, Alice get 2 + 7 = 9 piles in total. So we return 10 since it's larger.
Example 2:
Input: piles = [1,2,3,4,5,100]
Output: 104
Constraints:
1 <= piles.length <= 100
1 <= piles[i]<= 104
這道題是石頭遊戲系列的第二道,跟之前那道 Stone Game 不同的是終於換回了 Alice 和 Bob!還有就是取石子的方法,不再是隻能取首尾兩端的石子堆,而是可以取 [1, 2M] 範圍內的任意X堆,M是個變化的量,初始化為1,每次取完X堆後,更新為 M = max(M, X)
。這種取石子的方法比之前的要複雜很多,由於X的值非常的多,而且其不同的選擇還可能影響到M值,那麼整體的情況就特別的多,暴力搜尋基本上是行不通的。這種不同狀態之間轉移的問題用動態規劃 Dynamic Programming 是比較合適的,首先來考慮 DP 陣列的定義,題目要求的是 Alice 最多能拿到的石子個數,拿石子的方式是按順序的,不能跳著拿,所以決定某個狀態的是兩個變數,一個是當前還剩多少石子堆,可以通過當前位置座標i來表示,另一個是當前的m值,只有知道了當前的m值,那麼選手才知道能拿的堆數的範圍,所以 DP 就是個二維陣列,其 dp[i][m] 表示的意義在上面已經解釋了。接下來考慮狀態轉移方程,由於在某個狀態時已經知道了m值,則當前選手能拿的堆數在範圍 [1, 2m] 之間,為了更新這個 dp 值,所有x的情況都要遍歷一遍,即在剩餘堆數中拿x堆,但此時x堆必須小於等於剩餘的堆數,即 i + x <= n
dp[i + x][max(m, x)])
。為了快速得知當前剩餘的石子總數,需要建立累加和陣列,注意這裡是建立反向的累加和陣列,其中 sums[i] 表示範圍 [i, n-1] 之和。分析到這裡就可以寫出狀態狀態轉移方程如下:
dp[i][m] = max(dp[i][m], sums[i] - dp[i + x][max(m, x)])
接下來就是一些初始化和邊界定義的問題需要注意的了,dp 陣列大小為 n+1 by n+1,因為選手是可能一次將n堆都拿了,比如 n=1 時,所以 dp[i][n] 是存在的,且需要用 sums[i] 來初始化。更新 dp 時需要用三個 for 迴圈,分別控制i,m,和 x,注意更新從後往前遍歷i和m,因為我們要先更新小區間,再更新大區間。x的範圍要設定為 x <= 2 * m && i + x <= n
,前面也講過原因了,最後的答案儲存在 dp[0][1] 中返回即可,參見程式碼如下:
解法一:
class Solution {
public:
int stoneGameII(vector<int>& piles) {
int n = piles.size();
vector<int> sums = piles;
vector<vector<int>> dp(n + 1, vector<int>(n + 1));
for (int i = n - 2; i >= 0; --i) {
sums[i] += sums[i + 1];
}
for (int i = 0; i < n; ++i) {
dp[i][n] = sums[i];
}
for (int i = n - 1; i >= 0; --i) {
for (int m = n - 1; m >= 1; --m) {
for (int x = 1; x <= 2 * m && i + x <= n; ++x) {
dp[i][m] = max(dp[i][m], sums[i] - dp[i + x][max(m, x)]);
}
}
}
return dp[0][1];
}
};
我們再來用遞迴加記憶陣列的方式來實現一下,其實核心思想跟上面完全一樣,這裡就不過多的講解了,直接看程式碼吧:
解法二:
class Solution {
public:
int stoneGameII(vector<int>& piles) {
int n = piles.size();
vector<int> sums = piles;
vector<vector<int>> memo(n, vector<int>(n));
for (int i = n - 2; i >= 0; --i) {
sums[i] += sums[i + 1];
}
return helper(sums, 0, 1, memo);
}
int helper(vector<int>& sums, int i, int m, vector<vector<int>>& memo) {
if (i + 2 * m >= sums.size()) return sums[i];
if (memo[i][m] > 0) return memo[i][m];
int res = 0;
for (int x = 1; x <= 2 * m; ++x) {
int cur = sums[i] - sums[i + x];
res = max(res, cur + sums[i + x] - helper(sums, i + x, max(x, m), memo));
}
return memo[i][m] = res;
}
};
Github 同步地址:
https://github.com/grandyang/leetcode/issues/1140
類似題目:
參考資料:
https://leetcode.com/problems/stone-game-ii/
https://leetcode.com/problems/stone-game-ii/discuss/345247/C%2B%2B-DP-(Tabulation)
https://leetcode.com/problems/stone-game-ii/discuss/345230/JavaPython-DP-Solution