Alex and Lee play a game with piles of stones. There are an even number of piles arranged in a row, and each pile has a positive integer number of stones `piles[i]`.
The objective of the game is to end with the most stones. The total number of stones is odd, so there are no ties.
Alex and Lee take turns, with Alex starting first. Each turn, a player takes the entire pile of stones from either the beginning or the end of the row. This continues until there are no more piles left, at which point the person with the most stones wins.
Assuming Alex and Lee play optimally, return True
if and only if Alex wins the game.
Example 1:
Input: [5,3,4,5]
Output: true
Explanation:
Alex starts first, and can only take the first 5 or the last 5.
Say he takes the first 5, so that the row becomes [3, 4, 5].
If Lee takes 3, then the board is [4, 5], and Alex takes 5 to win with 10 points.
If Lee takes the last 5, then the board is [3, 4], and Alex takes 4 to win with 9 points.
This demonstrated that taking the first 5 was a winning move for Alex, so we return true.
Note:
2 <= piles.length <= 500
piles.length
is even.1 <= piles[i] <= 500
sum(piles)
is odd.
這道題說是有偶數堆的石子,每堆的石子個數可能不同,但石子總數是奇數個。現在 Alex 和 Lee (不應該是 Alice 和 Bob 么??)兩個人輪流選石子堆,規則是每次只能選開頭和末尾中的一堆,最終獲得石子總數多的人獲勝。若 Alex 先選,兩個人都會一直做最優選擇,問我們最終 Alex 是否能獲勝。博主最先想到的方法是像 [Predict the Winner](http://www.cnblogs.com/grandyang/p/6369688.html) 中的那樣,用個 player 變量來記錄當前是哪個玩家在操作,若為0,表示 Alex 在選,那么他只有兩種選擇,要么拿首堆,要么拿尾堆,兩種情況分別調用遞歸,兩個遞歸函數只要有一個能返回 true,則表示 Alex 可以獲勝,還需要用個變量 cur0 來記錄當前 Alex 的石子總數。同理,若 Lee 在選,即 player 為1的時候,也是只有兩種選擇,分別調用遞歸,兩個遞歸函數只要有一個能返回 true,則表示 Lee 可以獲勝,用 cur1 來記錄當前 Lee 的石子總數。需要注意的是,當首堆或尾堆被選走了后,我們需要標記,這里就有兩種方法,一種是從原 piles 中刪除選走的堆(或者是新建一個不包含選走堆的數組),但是這種方法會包括大量的拷貝運算,無法通過 OJ。另一種方法是用兩個指針 left 和 right,分別指向首尾的位置。當選取了首堆時,則 left 自增1,若選了尾堆時,則 right 自減1。這樣就不用執行刪除操作,或是拷貝數組了,大大的提高了運行效率,參見代碼如下:
解法一:
class Solution {
public:
bool stoneGame(vector<int>& piles) {
return helper(piles, 0, 0, 0, (int)piles.size() - 1, 0);
}
bool helper(vector<int>& piles, int cur0, int cur1, int left, int right, int player) {
if (left > right) return cur0 > cur1;
if (player == 0) {
return helper(piles, cur0 + piles[left], cur1, left + 1, right, 1) || helper(piles, cur0 + piles[right], cur1, left + 1, right, 1);
} else {
return helper(piles, cur0, cur1 + piles[left], left, right - 1, 0) || helper(piles, cur0, cur1 + piles[right], left, right - 1, 0);
}
}
};
這道題也可以使用動態規划 Dynamic Programming 來做,由於玩家獲勝的規則是拿到的石子數多,那么多的石子數就可以量化為 dp 值。所以我們用一個二維數組,其中 dp[i][j] 表示在區間 [i, j] 內 Alex 比 Lee 多拿的石子數,若為正數,說明 Alex 拿得多,若為負數,則表示 Lee 拿得多。則最終只要看 dp[0][n-1] 的值,若為正數,則 Alex 能獲勝。現在就要找狀態轉移方程了,我們想,在區間 [i, j] 內要計算 Alex 比 Lee 多拿的石子數,在這個區間內,Alex 只能拿i或者j位置上的石子,那么當 Alex 拿了 piles[i] 的話,等於 Alex 多了 piles[i] 個石子,此時區間縮小成了 [i+1, j],此時應該 Lee 拿了,此時根據我們以往的 DP 經驗,應該調用子區間的 dp 值,沒錯,但這里 dp[i+1][j] 表示是在區間 [i+1, j] 內 Alex 多拿的石子數,但是若區間 [i+1, j] 內 Lee 先拿的話,其多拿的石子數也應該是 dp[i+1][j],因為兩個人都要最優化拿,那么 dp[i][j] 的值其實可以被 piles[i] - dp[i+1][j] 更新,因為 Alex 拿了 piles[i],減去 Lee 多出的 dp[i+1][j],就是區間 [i, j] 中 Alex 多拿的石子數。同理,假如 Alex 先拿 piles[j],那么就用 piles[j] - dp[i][j-1] 來更新 dp[i][j],則我們用二者的較大值來更新即可。注意開始的時候要把 dp[i][i] 都初始化為 piles[i],還需要注意的是,這里的更新順序很重要,是從小區間開始更新,在之前那道 [Burst Balloons](http://www.cnblogs.com/grandyang/p/5006441.html),博主詳細的講了這種 dp 的更新順序,可以去看看,參見代碼如下:
解法二:
class Solution {
public:
bool stoneGame(vector<int>& piles) {
int n = piles.size();
vector<vector<int>> dp(n, vector<int>(n));
for (int i = 0; i < n; ++i) dp[i][i] = piles[i];
for (int len = 1; len < n; ++len) {
for (int i = 0; i < n - len; ++i) {
int j = i + len;
dp[i][j] = max(piles[i] - dp[i + 1][j], piles[j] - dp[i][j - 1]);
}
}
return dp[0][n - 1] > 0;
}
};
其實這道題是一道腦筋急轉彎題,跟之前那道 [Nim Game](http://www.cnblogs.com/grandyang/p/4873248.html) 有些像。原因就在於題目中的一個條件,那就是總共有偶數堆,那么就是可以分為堆數相等的兩堆,比如我們按奇偶分為兩堆。題目還說了石子總數為奇數個,那么分出的這兩堆的石子總數一定是不相等的,那么我們只要每次一直取石子總數多的奇數堆或者偶數堆,Alex 就一定可以躺贏,所以最叼的方法就是直接返回 true。我。。我。。我王司徒。。表示不服。。。我從未見過如此。。機智過人。。的解法~
解法三:
class Solution {
public:
bool stoneGame(vector<int>& piles) {
return true;
}
};
Github 同步地址:
https://github.com/grandyang/leetcode/issues/877
類似題目:
Guess Number Higher or Lower II
參考資料:
https://leetcode.com/problems/stone-game/
https://leetcode.com/problems/stone-game/discuss/154610/DP-or-Just-return-true
[LeetCode All in One 題目講解匯總(持續更新中...)](https://www.cnblogs.com/grandyang/p/4606334.html)