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]
.html
The objective of the game is to end with the most stones. The total number of stones is odd, so there are no ties.git
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.github
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 中的那樣,用個 player 變量來記錄當前是哪一個玩家在操做,若爲0,表示 Alex 在選,那麼他只有兩種選擇,要麼拿首堆,要麼拿尾堆,兩種狀況分別調用遞歸,兩個遞歸函數只要有一個能返回 true,則表示 Alex 能夠獲勝,還須要用個變量 cur0 來記錄當前 Alex 的石子總數。同理,若 Lee 在選,即 player 爲1的時候,也是隻有兩種選擇,分別調用遞歸,兩個遞歸函數只要有一個能返回 true,則表示 Lee 能夠獲勝,用 cur1 來記錄當前 Lee 的石子總數。須要注意的是,當首堆或尾堆被選走了後,咱們須要標記,這裏就有兩種方法,一種是從原 piles 中刪除選走的堆(或者是新建一個不包含選走堆的數組),可是這種方法會包括大量的拷貝運算,沒法經過 OJ。另外一種方法是用兩個指針 left 和 right,分別指向首尾的位置。當選取了首堆時,則 left 自增1,若選了尾堆時,則 right 自減1。這樣就不用執行刪除操做,或是拷貝數組了,大大的提升了運行效率,參見代碼以下:指針
解法一:code
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,博主詳細的講了這種 dp 的更新順序,能夠去看看,參見代碼以下:htm
解法二:blog
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 有些像。緣由就在於題目中的一個條件,那就是總共有偶數堆,那麼就是能夠分爲堆數相等的兩堆,好比咱們按奇偶分爲兩堆。題目還說了石子總數爲奇數個,那麼分出的這兩堆的石子總數必定是不相等的,那麼咱們只要每次一直取石子總數多的奇數堆或者偶數堆,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