You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

183 lines
6.2 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package class_2023_03_2_week;
// 爱丽丝和鲍勃继续他们的石子游戏
// 许多堆石子 排成一行,每堆都有正整数颗石子 piles[i]
// 游戏以谁手中的石子最多来决出胜负。
// 爱丽丝和鲍勃轮流进行爱丽丝先开始。最初M = 1。
// 在每个玩家的回合中,该玩家可以拿走剩下的 前 X 堆的所有石子,其中 1 <= X <= 2M
// 然后,令 M = max(M, X)。
// 游戏一直持续到所有石子都被拿走。
// 假设爱丽丝和鲍勃都发挥出最佳水平
// 返回爱丽丝可以得到的最大数量的石头。
// 测试链接 : https://leetcode.cn/problems/stone-game-ii/
public class Code01_StoneGameII {
// 暴力方法
// 提交会超时,但是怎么尝试已经详细阐述明白
// 提交时把stoneGameII1改名为stoneGameII
public static int stoneGameII1(int[] piles) {
return first1(piles, 0, 1);
}
// 当前先手的情况下
// 可以在piles[i....]范围上拿数字
// 返回最大的收益
public static int first1(int[] piles, int index, int m) {
// 没有数字了收益肯定是0
if (index == piles.length) {
return 0;
}
int best = 0;
int pre = 0;
// 先手根据m做所有的尝试选最好的
// 1 2 3 .. 2m
// index....i 几个j个
// 1) index...index 1个 pre
// 2) index...index+1 2个 pre
// 3) index...index+2 3个 pre
// m = 100 1 2 3 ~ 200 1~2m
for (int i = index, j = 1; i < piles.length && j <= 2 * m; i++, j++) {
pre += piles[i];
// j个
// index...i (i+1....
best = Math.max(best, pre + second1(piles, i + 1, Math.max(j, m)));
}
return best;
}
// 当前后手的情况下
// 可以在piles[i....]范围上拿数字
// 返回最大的收益
public static int second1(int[] piles, int index, int m) {
if (index == piles.length) {
return 0;
}
int worse = Integer.MAX_VALUE;
// 先手根据m做所有的尝试并且把最差的结果留给后手
for (int i = index, j = 1; i < piles.length && j <= 2 * m; i++, j++) {
worse = Math.min(worse, first1(piles, i + 1, Math.max(j, m)));
}
return worse;
}
// 暴力方法改成记忆化搜索
// 可以直接通过
// 提交时把stoneGameII2改名为stoneGameII
public static int stoneGameII2(int[] piles) {
int[][] f = new int[piles.length][piles.length + 1];
int[][] s = new int[piles.length][piles.length + 1];
for (int i = 0; i < piles.length; i++) {
for (int j = 0; j <= piles.length; j++) {
f[i][j] = -1;
s[i][j] = -1;
}
}
return first2(piles, 0, 1, f, s);
}
// 当前先手的情况下
// 可以在piles[i....]范围上拿数字
// 返回最大的收益
public static int first2(int[] piles, int index, int m, int[][] f, int[][] s) {
// 没有数字了收益肯定是0
if (index == piles.length) {
return 0;
}
if (f[index][m] != -1) {
return f[index][m];
}
int best = 0;
int pre = 0;
for (int i = index, j = 1; i < piles.length && j <= 2 * m; i++, j++) {
pre += piles[i];
best = Math.max(best, pre + second2(piles, i + 1, Math.min(piles.length, Math.max(j, m)), f, s));
}
f[index][m] = best;
return best;
}
// 当前后手的情况下
// 可以在piles[i....]范围上拿数字
// 返回最大的收益
public static int second2(int[] piles, int index, int m, int[][] f, int[][] s) {
if (index == piles.length) {
return 0;
}
if (s[index][m] != -1) {
return s[index][m];
}
int worse = Integer.MAX_VALUE;
for (int i = index, j = 1; i < piles.length && j <= 2 * m; i++, j++) {
worse = Math.min(worse, first2(piles, i + 1, Math.min(piles.length, Math.max(j, m)), f, s));
}
s[index][m] = worse;
return worse;
}
// 记忆化搜索改严格位置依赖的动态规划
// 提交时把stoneGameII3改名为stoneGameII
public static int stoneGameII3(int[] piles) {
int n = piles.length;
int[][] f = new int[n + 1][n + 1];
int[][] s = new int[n + 1][n + 1];
for (int index = n - 1; index >= 0; index--) {
for (int m = 1; m <= n; m++) {
int pre = 0;
for (int i = index, j = 1; i < piles.length && j <= 2 * m; i++, j++) {
pre += piles[i];
f[index][m] = Math.max(f[index][m], pre + s[i + 1][Math.min(n, Math.max(j, m))]);
}
s[index][m] = Integer.MAX_VALUE;
for (int i = index, j = 1; i < piles.length && j <= 2 * m; i++, j++) {
s[index][m] = Math.min(s[index][m], f[i + 1][Math.min(n, Math.max(j, m))]);
}
}
}
return f[0][1];
}
// 严格位置依赖的动态规划,一张表的版本
// 提交时把stoneGameII4改名为stoneGameII
public int stoneGameII4(int[] piles) {
int n = piles.length, sum = 0;
// dp[i][m] : 表示piles[i....]范围上在拿取范围是1~2*m的情况下先手能获得的最大收益
// 举个例子 :
// 比如当前先手可以在piles[7...]范围上在拿取范围是1 ~ 2 * 4(m)的情况下
// 先手想获得最大收益
// 当先手拿走7...9的块(这是长度为3的块)
// 后续轮到对手在piles[10...]范围上在拿取范围是1~2*m的情况下对手也会获得的最大收益
// 假设piles[7...]整体的累加和是sum
// 整体的累加和 = 先手收益 + 后手收益,因为先手和后手一定刮分整个累加和
// 所以,先手获得的最大值 = sum - 对手在先手时后续所有可能性中的最小值
int[][] dp = new int[n][n + 1];
for (int i = n - 1; i >= 0; i--) {
// i == n - 1 sum = arr[n-1]
// i == n - 2 sum += arr[n-2]
// i == n - 3 sum += arr[n-3]
sum += piles[i];
for (int m = 1; m <= n; m++) {
// dp[4][1] ...
// dp[4][2] ...
// dp[i][m]
if (i + 2 * m >= n) {
// 如果先手当前的拿取范围,可以全包下数组所有的数
// 那必然是全拿,不给对手任何机会
dp[i][m] = sum;
} else {
// 如果先手当前的拿取范围,不能全包下数组所有的数
// 那必然去是看看,对手在先手时后续所有可能性中的最小值
// nextMin就是这个含义 : 对手在先手时后续所有可能性中的最小值
int nextMin = Integer.MAX_VALUE;
for (int x = 1; x <= 2 * m; x++) {
nextMin = Math.min(nextMin, dp[i + x][Math.max(m, x)]);
}
// 先手获得的最大值 = sum - 对手在先手时后续所有可能性中的最小值
dp[i][m] = sum - nextMin;
}
}
}
return dp[0][1];
}
}