這道算法題其實並不難,若是你把文章從頭至尾看完的話基本上能看懂,但若是你看到最後的話大機率會說一句:這是什麼沙雕題目?!java
題目來源於 LeetCode 第 877 號問題:石子游戲。算法
爲了更好理解,我改編了一下題目,描述是這樣的:編程
喜羊羊和灰太狼用幾堆石子在作遊戲。偶數堆石子排成一行,每堆都有正整數顆石子 piles[i]
。spa
遊戲以誰手中的石子最多來決出勝負。石子的總數是奇數,因此沒有平局。code
喜羊羊和灰太狼輪流進行,喜羊羊先開始。 每回合,玩家從行的開始或結束處取走整堆石頭。 這種狀況一直持續到沒有更多的石子堆爲止,此時手中石子最多的玩家獲勝。cdn
假設喜羊羊和灰太狼都發揮出最佳水平,當喜羊羊贏得比賽時返回 true
,當灰太狼贏得比賽時返回 false
。blog
舉兩個例子來幫助理解題意。遞歸
輸入:[ 5,3,4,5 ]遊戲
輸出:true數學
解釋:
喜羊羊先開始,只能拿前 5 顆或後 5 顆石子 。
假設他取了前 5 顆,這一行就變成了 [ 3 ,4,5 ] 。
若是灰太狼拿走前 3 顆,那麼剩下的是 [ 4,5 ],喜羊羊拿走後 5 顆贏得 10 分。
若是灰太狼拿走後 5 顆,那麼剩下的是 [ 3,4 ],喜羊羊拿走後 4 顆贏得 9 分。
這代表,取前 5 顆石子對喜羊羊來講是一個勝利的舉動,因此咱們返回 true 。
輸入:[ 5,10000,2,3 ]
輸出:true
解釋:
喜羊羊先開始,只能拿前 5 顆或後 3 顆石子 。
假設他取了後 3 顆,這一行就變成了 [ 5,10000,2 ]。
灰太狼確定會在剩下的這一行中取走前 5 顆,這一行就變成了 [ 10000,2 ]。
而後喜羊羊取走前 10000 顆,總雙贏得 10003 分,灰太狼贏得 7 分。
這代表,取後 3 顆石子對喜羊羊來講是一個勝利的舉動,因此咱們返回 true 。
這個例子代表,並非須要每次都挑選最大的那堆石頭。
涉及到最優解的問題,那麼確定要去嘗試一下使用 **動態規劃 **來解決了。
先看一下力扣的正規題解:
讓咱們改變遊戲規則,使得每當灰太狼得分時,都會從喜羊羊的分數中扣除。
令 dp(i, j)
爲喜羊羊能夠得到的最大分數,其中剩下的堆中的石子數是 piles[i], piles[i+1], ..., piles[j]
。這在比分遊戲中很天然:咱們想知道遊戲中每一個位置的值。
咱們能夠根據 dp(i + 1,j)
和 dp(i,j-1)
來制定 dp(i,j)
的遞歸,咱們可使用動態編程以不重複這個遞歸中的工做。(該方法能夠輸出正確的答案,由於狀態造成一個DAG(有向無環圖)。)
當剩下的堆的石子數是 piles[i], piles[i+1], ..., piles[j]
時,輪到的玩家最多有 2 種行爲。
能夠經過比較 j-i
和 N modulo 2
來找出輪到的人。
若是玩家是喜羊羊,那麼它將取走 piles[i]
或 piles[j]
顆石子,增長它的分數。以後,總分爲 piles[i] + dp(i+1, j)
或 piles[j] + dp(i, j-1)
;咱們想要其中的最大可能得分。
若是玩家是灰太狼,那麼它將取走 piles[i]
或 piles[j]
顆石子,減小喜羊羊這一數量的分數。以後,總分爲 -piles[i] + dp(i+1, j)
或 -piles[j] + dp(i, j-1)
;咱們想要其中的最小可能得分。
代碼以下:
上面的代碼並不算複雜,固然,若是你看不懂也不要緊,不影響解決問題,請看下面的數學分析。
由於石頭的數量是奇數,所以只有兩種結果,輸或者贏。
喜羊羊先開始拿石頭,隨便拿!而後比較石頭數量:
因此代碼以下:
class Solution {
public boolean stoneGame(int[] piles) {
return true;
}
}
複製代碼
看完以後,你的心情是怎麼樣的?
此題的LeetCode 的評論區裏一片吐槽:這是什麼沙雕題目!
可能搞過 ACM 等競賽的人都會微微一笑:不會幾萬個套路怎麼好意思說本身是 acmer 。咱們這些普通人爲之驚奇的題目,到他們這裏就是完全被玩壞了,各類稀奇古怪的秒解。