1. 程式人生 > 實用技巧 >877. 石子游戲

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 。

提示:

2 <= piles.length <= 500

piles.length 是偶數。

1 <= piles[i] <= 500

sum(piles) 是奇數。

來源:力扣(LeetCode)

連結:https://leetcode-cn.com/problems/stone-game

思路

①定義dp陣列

根據題意:每回合,玩家從行的開始或結束處取走整堆石頭,因此石頭對陣列只能取開頭或者結尾。

dp[i][j]表示剩下的石頭堆從第i堆到第j堆時本輪玩家相對於對方玩家多的石頭數量,因此每輪的玩家都希望自己這一輪的dp[i][j]儘可能大於對方

②確定遞推公式

已知piles[0]=dp[0][0]、piles[1]=dp[1][1]

如何得到dp[0][1]

如果只有一堆石頭,p1先手選dp[0][0]

如果有兩堆石頭piles[0]和piles[1],當p1選擇了之後p2就會選之前p1的策略

piles[0]-dp[1][1]表示p1先手選了第一堆石頭後,p2的最佳策略是選擇第二堆石頭

piles[1]-dp[0][0]表示p1先手選了第二堆石頭後,p2的最佳策略是選擇第一堆石頭

每個人當前要做出最優策略,因此要比較哪個好,所以用Math.max函式來比較

如果有三堆石頭,piles[0],piles[1],piles[2]

p1選了第一堆石頭:piles[0]-dp[1][2]

p1選擇第三堆石頭:piles[2]-dp[0][1]

pile[0]-dp[1][2]代表著,p1拿取了[0]個石頭堆後,減去p2在[1]~[2]相鄰石頭堆情況下贏得的分數

最終得到

dp[i][j]=Math.max(piles[i]-dp[i+1][j],piles[j]-piles[i][j-1])

③邊界定義

dp[i][i]=piles[i]

class Solution {
    public boolean stoneGame(int[] piles) {
        int n=piles.length;
        int [][]dp=new int[n][n];
        //初始化dp[i][i]
        for(int i=0;i<n;i++)
            dp[i][i]=piles[i];
        /**注意這裡的兩個迴圈第一個迴圈dis代表間隔距離,比如說dis=1時,配合接下來i的迴圈,會不斷得到相鄰2個石頭堆的最優選擇策略,比如說{1,2,3,4}時,會得到{1,2}、{2、3}、{3、4}的最優選擇策略;當dis=2時,會得到相鄰3個石頭堆的最優選擇策略,得到{1,2,3}、{2、3、4}。**/
        for(int dis=1;dis<n;dis++)
            for(int i=0;i<n-dis;i++)//i仍然表示起始位置
                dp[i][i+dis]=Math.max(piles[i]-dp[i+1][i+dis],piles[i+dis]-dp[i][i+dis-1]);
        return dp[0][n-1]>0;
    }
}