一道看完答案你會覺得很沙雕的「動態規劃演算法題」

程式設計師吳師兄發表於2019-03-08

這道演算法題其實並不難,如果你把文章從頭到尾看完的話基本上能看懂,但如果你看到最後的話大概率會說一句:這是什麼沙雕題目?!

題目來源於 LeetCode 第 877 號問題:石子游戲。

為了更好理解,我改編了一下題目,描述是這樣的:

題目描述

喜羊羊和灰太狼用幾堆石子在做遊戲。偶數堆石子排成一行,每堆都有正整數顆石子 piles[i]

遊戲以誰手中的石子最多來決出勝負。石子的總數是奇數,所以沒有平局。

喜羊羊和灰太狼輪流進行,喜羊羊先開始。 每回合,玩家從行的開始或結束處取走整堆石頭。 這種情況一直持續到沒有更多的石子堆為止,此時手中石子最多的玩家獲勝。

假設喜羊羊和灰太狼都發揮出最佳水平,當喜羊羊贏得比賽時返回 true ,當灰太狼贏得比賽時返回 false

題目分析

舉兩個例子來幫助理解題意。

例子一:

輸入:[ 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-iN 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);我們想要其中的最小可能得分。

程式碼如下:

圖 1

上面的程式碼並不算複雜,當然,如果你看不懂也沒關係,不影響解決問題,請看下面的數學分析。

數學分析

因為石頭的數量是奇數,因此只有兩種結果,輸或者贏。

喜羊羊先開始拿石頭,隨便拿!然後比較石頭數量:

  1. 如果石頭數量多於對手,贏了;
  2. 如果石頭數量少於對手,自己拿石頭的順序和對手拿石頭的順序對調,還是贏。

所以程式碼如下:

class Solution {
    public boolean stoneGame(int[] piles) {
        return true;
    }
}
複製程式碼

看完之後,你的心情是怎麼樣的?

此題的LeetCode 的評論區裡一片吐槽:這是什麼沙雕題目!

可能搞過 ACM 等競賽的人都會微微一笑:不會幾萬個套路怎麼好意思說自己是 acmer 。我們這些普通人為之驚奇的題目,到他們這裡就是徹底被玩壞了,各種稀奇古怪的秒解。

相關文章