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

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

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

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

題目描述

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

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

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

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

題目分析

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

例子一:

輸入:[ 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 。咱們這些普通人爲之驚奇的題目,到他們這裏就是完全被玩壞了,各類稀奇古怪的秒解。

相關文章
相關標籤/搜索