題目來源:力扣
題目介紹:
亞歷克斯和李繼續他們的石子游戲。許多堆石子 排成一行,每堆都有正整數顆石子 piles[i]。遊戲以誰手中的石子最多來決出勝負。
亞歷克斯和李輪流進行,亞歷克斯先開始。最初,M = 1。
在每個玩家的回合中,該玩家可以拿走剩下的 前 X 堆的所有石子,其中 1 <= X <= 2M。然後,令 M = max(M, X)。
遊戲一直持續到所有石子都被拿走。
假設亞歷克斯和李都發揮出最佳水平,返回亞歷克斯可以得到的最大數量的石頭。
============================================================
示例:
輸入:piles = [2,7,9,4,4]
輸出:10
解釋:
如果亞歷克斯在開始時拿走一堆石子,李拿走兩堆,接着亞歷克斯也拿走兩堆。在這種情況下,亞歷克斯可以拿到 2 + 4 + 4 = 10 顆石子。
如果亞歷克斯在開始時拿走兩堆石子,那麼李就可以拿走剩下全部三堆石子。在這種情況下,亞歷克斯可以拿到 2 + 7 = 9 顆石子。
所以我們返回更大的 10。
====================================================
審題:
本題屬於雙人博弈問題,對於雙人博弈問題,如果可以使用動態規劃方法解決,我們可以添加狀態變量表示當前正在參與遊戲的對象, 這樣處理方式在博弈問題中是通用的.大家可以嘗試一下.如石子游戲這篇文章中的方法類似.
對於該問題,分析其最優子結構.假設當前剩餘石子爲堆,當前用戶可以選擇堆,.爲了確定用戶當前所做的最優選擇,我們需要知道用戶在剩餘堆後最優方案下所能取得的最優方案.對於每一, , 如果我們知道了用戶在堆時的最優方案,則我們可以確定當前用戶的最優選擇.因此,該問題存在最優子結構.若當前問題爲剩餘石子爲堆時的最優方案,其子問題爲所有剩餘堆數小於堆時的最優方案. 基於以上最優子結構,不能得出,其子問題存在重疊.因此我們可以考慮使用動態規劃方法進行解決.
-
確定狀態變量
該問題中最容易分析的一個狀態量是當前石堆的狀態.由於每次均從起始端一端選擇,因此我們可是使用當前剩餘石堆起始堆編號來描述當前石堆狀態(也可使用剩餘石堆數來描述當前狀態).由於用戶每一次能夠選擇的石堆數並不是固定的取值範圍,取決於上一次的選擇.因此我們需要一個狀態量來描述當前其可選的石堆數,這個量就是題目中的M.最後,對於雙人博弈問題,我們需要一個狀態量來區分正在參與遊戲的選手與處於等待狀態的選手.石堆狀態變量的可選範圍爲;狀態變量的取值範圍與當前石堆狀態變量相關.如果前面每一用戶每一步都選擇1個石堆,則的最小值是1,如果前一用戶一次性拿走了堆石子,則當前M的最大值爲, 因此狀態變量的取值範圍爲:.最後一個狀態量取值爲. 因此我們可是使用三維數組存儲在任意狀態下所能獲得的最多石子數.
S[i][m][0]
表示當前石堆起始堆編號爲i, M取值爲m時,正在參與遊戲的用戶所能取得的最多石子個數.S[i][m][1]
表示當前石堆時期編號爲i, M取值爲m,處於等待狀態的用戶所能取得的最多石子個數. -
確定狀態轉移方程
若遊戲用戶當前最優選擇爲x,則 -
確定基礎狀態
如果只剩一堆石子,即i = piles.length-1, 則S[i][m][0] = piles[piles.length-1], S[i][m][1] = 0; 對於所有的m, .
特別需要注意的是, 狀態變量的取值範圍爲 這一條件並不適用與i=0時的情形,因爲i=0時, M =1;
S[0][1][0] = max{piles[0] + S[1][1][1], piles[0]+piles[1] + S[2][2][1]}
java算法:
class Solution {
//定義前綴數組
//狀態變量, 當前剩餘石子堆數起始編號, 當前M值, 當前執行用戶
//S[i][m][0] = max{cumSum(piles[i], ...piles[i+x-1]) + S[i+x][max(x, m)][1];1<=x<=2M}
//S[i][m][1] = S[i+x][max(x, m)][0]
public int stoneGameII(int[] piles) {
int[] cumSum = new int[piles.length];
cumSum[0] = piles[0];
for(int i = 1; i < piles.length; i++)
cumSum[i] = cumSum[i-1] + piles[i];
int[][][] S = new int[piles.length+1][piles.length][2];
//由於M = max(M, X), 因此如果當前起始堆下標爲s,則M最大值爲s, 表示第一用戶一次性選擇s堆
//如果當前僅剩一堆,則無論當前M如何,第一個用戶將拿走該堆,第二個用戶爲空
for(int m = 1, s = piles.length-1; m <= s; m++){
S[piles.length-1][m][0] = piles[piles.length-1];
S[piles.length-1][m][1] = 0;
}
for(int s = piles.length-2; s > 0; s--){
for(int m = 1; m <= s; m++){
//當前用戶可選擇的x
S[s][m][0] = 0;
int maxX = 1;
for(int x = 1; x <= 2*m && x <= piles.length-s; x++){
int stones = S[s+x][Math.max(x, m)][1] + cumSum[s+x-1] - cumSum[s-1];
if(S[s][m][0] < stones){
S[s][m][0] = stones;
maxX = x;
}
}
S[s][m][1] = S[s+maxX][Math.max(maxX, m)][0];
}
}
//x = 1;
if(piles.length > 1){
S[0][1][0] = Math.max(piles[0] + S[1][1][1], piles[0]+piles[1] + S[2][2][1]);
return S[0][1][0];
}
else{
return piles[0];
}
}
}