1. 程式人生 > >leetcode 第877題 石子游戲 python解法(用時420ms)

leetcode 第877題 石子游戲 python解法(用時420ms)

leetcode 第877題 石子游戲 python解法(用時420ms)

該題是典型的雙人回合制博弈遊戲,兩個人輪流從石子堆中拿出石子,其中石子都是按行一行行排好的,每個回合只能從頭或者尾拿出一行石子。石子排列的行數是偶數,而且石子的總數是奇數,這樣說明到最後不會出現平局。

問題分析

動態規劃

這題本意是要求使用動態規劃,動態規劃最重要的是要寫出狀態轉移方程。假設在某個狀態時石子的排列為[2,4,7,1,8],陣列中的每個數表示該行石子的總數。按照題目要求只能在頭或尾取石子,所以本回合能取的只有2或8,而到底取哪一個,這是要進行比較才能做出選擇。由於雙方都不是傻子,所以每個回合選手選取的數都是對自己最有利的,換句話說,是對對方最不利的。
所以,這裡我們選取其中某一個選手的角度展開問題,首先該選手會選取一個數作為自己的點數,所以是正值,接下來是對手的回合,他也會選取一個數,那麼我們要將第一個數減去這個數,如果是整數,說明第一位選手取得多(贏了),如果是負數說明比較少(輸了)。所以狀態轉移方程是 dp[i][j]=max(piles[i] - dp[i+1][j], piles[j] - dp[i][j-1])

。狀態方程要使用max()函式,這裡涉及到另一個知識點即:極小化極大演算法(Minimax,關於這部分大家可以上網查一查別的資料,很有趣)。前面說過,雙方都不是傻子,所以每個回合都是結果最大的數,使用max()函式就能說明這點。這裡i和j,指代的是一堆石子(這裡用陣列替換)中的第i行以及第j行。然後dp[i][j]儲存的是從第i行到第j行選取後的最優結果。這個最優結果是選取首尾分別減去各自剩下的子陣列的最優結構得到的。其中注意的是如果i=j,表示陣列的長度為1,直接就可以返回piles[i]。
在程式開始之前,我們先要申請一個nxn(n是石子堆的長度)的二維陣列。行代表的是開始的位置座標,列是結束位置的座標。所以在二維陣列對角線上有i==j,所以可以直接將這些位置先賦值為piles[i](實際上用到的空間只有一半,大家可以想一想為什麼)。
為了使這個過程更加直觀一點,這裡使用[2,5,233,7]做個例子,首先先生成一個二維陣列,並在對角線上賦值。申請二維陣列

接下來,開始從後向前遍歷(從後向前遍歷,每次使用的都是已經計算好的結果,所以可以用迭代。而從前向後遍歷,每次的值都是下一步需要計算的,因此需要用到遞迴。使用遞迴雖然比較直觀,好實現,但是比較慢。所以這裡採用了迭代)。
計算的過程如下:迭代過程
一直計算下去,最後計算的是dp[0][3],是指原陣列的第一位到最後一位的最優解,它代表的是先手的選手得分減去後手的得分的結果,返回它是否大於零就好了。

邏輯判斷

這一題其實非常簡單,以至於評論裡很多人都在調侃這道題。先從題目給的條件出發,題目中說石子排列的行數是偶數,所以抽象的陣列的下標中奇數和偶數的個數一樣。然後題目中又說石子的總數是奇數,那麼最後所有行數為奇數的石子總數肯定不會等於偶數的石子總數-----要麼大於,要麼小於。最後題目又說必須在首或尾取石子,而且兩位選手都發揮最佳水平。這樣一來,只要先手的選手事先計算是行數為奇數的石子總數多,還是為偶數的總數最多,然後就可以根據規則,每個回合都可以確保自己能夠取到奇數的行數或偶數的行數,立於不敗之地。以piles=[2,3,233,56,9,7]這個來做為例子。先手的人通過計算可以得到,下標為偶數的數字總和相加要大於奇數的數字總和。所以開始先取piles[0]=2,下個回合輪到對手來取,這時候他只能選擇piles[1]或pikes[5](下標都是奇數,取不到偶數)。無論對手取的是哪一個數,先手的人在下個回合總是可以取到下標為偶數的元素,並保證下下個回合對手還是取得奇數下標元素。到最後先手的人取倒的全是下標為偶數的元素,而對手取到的是奇數的,這樣先手的人一定可以贏得比賽。
所以這道題偷懶的解法就是直接返回True就好了。

原始碼(Python)

class Solution:
    def stoneGame(self, piles):
        """
        :type piles: List[int]
        :rtype: bool
        """
        n = len(piles)
        dp = [[piles[i] if i == j else 0 for i in range(n)] for j in range(n)]
        for i in range(n-2, -1, -1):
            for j in range(i+1, n):
                dp[i][j] = max(piles[i] - dp[i+1][j], piles[j] - dp[i][j-1])
        return dp[0][n-1] > 0

我的Cara