本题出自力扣题库第1140题。题面大意如下:
甲乙两人玩石子游戏。许多堆石子排成一行,每堆都有正整数颗石子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值。两人的最佳策略都是使自己的石头总数最大化,因为题面要求是返回甲方最后可能得到的最多石头数,那么乙方的目标也可以理解为使甲方的石头总数最小化。因此我们的递归方法就需要第三个参数,用于表示当前的玩家是甲方还是乙方。相对应地,我们需要一个三维的DP数组来缓存计算结果。
以上的递归方法还可以进一步优化为在每一步都是当前玩家获得的石头数(A)和对家获得的石头数(B)只差(A - B)最大化,这样递归方法只需要二个参数。而所有石头的总和(A+B)加上(A-B)的最大值,然后除以2,就是最终的答案。
class Solution {
int[][] dp;
int[][] dpSum;
public int stoneGameII(int[] piles) {
int N = piles.length;
dpSum = new int[N][N];
for (int i = 0; i < N; i++) {
dpSum[i][i] = piles[i];
for (int j = i + 1; j < N; j ++) {
dpSum[i][j] = piles[j] + dpSum[i][j - 1] ;
}
}
if (N <= 2) {
return dpSum[0][N - 1];
}
dp = new int[N][N];
return (dpSum[0][N - 1] + helper(0, 1, piles, N)) / 2;
}
private int helper(int start, int M, int[] piles, int N) {
if (start >= N) {
return 0;
}
if (dp[start][M] != 0 ) {
return dp[start][M];
}
int best = Integer.MIN_VALUE;
for (int i = 1; i <= 2 * M && (start + i - 1) < N; i ++) {
int diff = dpSum[start][start + i - 1] - helper(start + i, Math.max(i, M), piles, N);
best = Math.max(best, diff);
}
return dp[start][M] = best;
}
}