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])
在程式開始之前,我們先要申請一個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