题目:给定一个整型数组arr,代表数值不同的纸牌排成一条线 玩家A和玩家B依次拿走每张纸牌 规定玩家A先拿,玩家B后拿 但是每个玩家每次只能拿走最左或最右的纸牌 玩家A和玩家B都绝顶聪明
代码中均有解析和注释:
public class GameMove {
/**
* @param brand 给定一个整型数组arr,代表数值不同的纸牌排成一条线
* 玩家A和玩家B依次拿走每张纸牌
* 规定玩家A先拿,玩家B后拿
* 但是每个玩家每次只能拿走最左或最右的纸牌
* 玩家A和玩家B都绝顶聪明
* @return 返回最后获胜者的分数。
*/
public static int win1(int[] brand) {
if (brand == null || brand.length == 0) return 0;
int first = before(brand, 0, brand.length - 1);
int second = after(brand, 0, brand.length - 1);
return Math.max(first, second);
}
private static int after(int[] brand, int start, int end) {
if (start == end) return 0;
if (start == end - 1) return Math.min(brand[start], brand[end]);
int result1 = before(brand, start + 1, end);
int result2 = before(brand, start, end - 1);
return Math.min(result1, result2);
}
private static int before(int[] brand, int start, int end) {
if (start == end) return brand[start];
if (start == end - 1) return Math.max(brand[start], brand[end]);
// 可能那最左边这张 也可能拿最右边这张 然后就是 后手的最好结果
int result1 = brand[start] + after(brand, start + 1, end);
int result2 = brand[end] + after(brand, start, end - 1);
return Math.max(result1, result2);
}
// 记忆化搜索
public static int win2(int[] brand) {
if (brand == null || brand.length == 0) return 0;
// 变化的值是start 和 end 都不会超过纸牌数量的长度
int[][] firstDp = new int[brand.length][brand.length];
int[][] secondDp = new int[brand.length][brand.length];
for (int i = 0; i < brand.length; i++) {
for (int j = 0; j < brand.length; j++) {
firstDp[i][j] = -1;
secondDp[i][j] = -1;
}
}
int first = before2(brand, 0, brand.length - 1, firstDp, secondDp);
int second = after2(brand, 0, brand.length - 1, firstDp, secondDp);
return Math.max(first, second);
}
private static int after2(int[] brand, int start, int end, int[][] firstDp, int[][] secondDp) {
if (secondDp[start][end] != -1) {
return secondDp[start][end];
}
int ans;
if (start == end) {
ans = 0;
} else if (start == end - 1) {
ans = Math.min(brand[start], brand[end]);
} else {
int result1 = before2(brand, start + 1, end, firstDp, secondDp);
int result2 = before2(brand, start, end - 1, firstDp, secondDp);
ans = Math.min(result1, result2);
}
secondDp[start][end] = ans;
return ans;
}
private static int before2(int[] brand, int start, int end, int[][] firstDp, int[][] secondDp) {
if (firstDp[start][end] != -1) {
return firstDp[start][end];
}
int ans;
if (start == end) {
ans = brand[start];
} else if (start == end - 1) {
ans = Math.max(brand[start], brand[end]);
} else {
// 可能那最左边这张 也可能拿最右边这张 然后就是 后手的最好结果
int result1 = brand[start] + after2(brand, start + 1, end, firstDp, secondDp);
int result2 = brand[end] + after2(brand, start, end - 1, firstDp, secondDp);
ans = Math.max(result1, result2);
}
firstDp[start][end] = ans;
return ans;
}
// 动态规划 分析
/**
* 当 start 大于 end 便是无效值 因为 纸牌最左 不可能大于最右边的纸牌的索引 所以 x 为无效数据
* 举例数组 int[] arr = [20,40,30,10]
* 索引 0 1 2 3
* firstDp: if (start == end) return brand[start]; 返回的便是 当前数组中的值
* secondDp: if (start == end) return 0; 返回的是 0
* firstDp: if (start == end - 1) return Math.max(brand[start], brand[end]);
* 既如此 那么firstDp start=0 end = 1 时 (0,1) (2,1) (3,2) 可直接得出
* 第三种情况 firstDp: int result1 = brand[start] + after(brand, start + 1, end);
* int result2 = brand[end] + after(brand, start, end - 1);
* return Math.max(result1, result2);
* 普遍位置依赖after方法 也就是 secondDp[start + 1][end] 和 secondDp[start][end - 1]
* 在firstDp中 * 代表一个随机位置 我们在secondDp找到相对应的位置 *
* 找寻一下 secondDp[start + 1][end] 和 secondDp[start][end - 1] 也就是 secondDp[1][2] -> 20 和 secondDp[0][1] -> 30
* 看看代码中所写 就是 数组元素加 after函数返回取最大值
* 也就是 int result1 = brand[start] + after(brand, start + 1, end) -> 20 + 30 = 50;
* 也就是 int result2 = brand[end] + after(brand, start, end - 1) -> 30 + 20 = 50;
* 在看after方法和 secondDp依赖 firstDp的位置 求法相同
* firstDp
* start\end 0 1 2 3
* 0 20 40 *(50)
* 1 x 40 40
* 2 x x 30 30
* 3 x x x 10
* secondDp
* start\end 0 1 2 3
* 0 0 20 *
* 1 x 0 30
* 2 x x 0 10
* 3 x x x 0
*/
public static int win3(int[] brand) {
if (brand == null || brand.length == 0) return 0;
// 变化的值是start 和 end 都不会超过纸牌数量的长度
int[][] firstDp = new int[brand.length][brand.length];
int[][] secondDp = new int[brand.length][brand.length];
// firstDP 和 secondDp 的最后一个位置 单独设置一下
// 放在for循环中 设置firstDp[i][i + 1]和secondDp[i][i + 1]时会越界
firstDp[brand.length - 1][brand.length - 1] = brand[brand.length - 1];
secondDp[brand.length - 1][brand.length - 1] = 0;
for (int i = 0; i < brand.length - 1; i++) {
firstDp[i][i + 1] = Math.max(brand[i], brand[i + 1]);
secondDp[i][i + 1] = Math.min(brand[i], brand[i + 1]);
firstDp[i][i] = brand[i];
secondDp[i][i] = 0;
}
for (int start = brand.length - 3; start >= 0; start--) {
for (int end = start + 2; end < brand.length; end++) {
firstDp[start][end] = Math.max(brand[start] + secondDp[start + 1][end], brand[end] + secondDp[start][end - 1]);
secondDp[start][end] = Math.min(firstDp[start + 1][end], firstDp[start][end - 1]);
}
}
return Math.max(firstDp[0][brand.length - 1], secondDp[0][brand.length - 1]);
}
// 空间压缩版本
public static int win4(int[] brand) {
if (brand == null || brand.length == 0) return 0;
int[] firstDp = new int[brand.length];
int[] secondDp = new int[brand.length];
// 从下往上 从左往右 左边的起始点就是start==end + 1开始 start==end时候 firstDp已经渲染了值了 secondDp不需要设置 因为是0;
for (int start = brand.length - 1; start >=0 ; start--) {
firstDp[start] = brand[start];
for (int end = start + 1; end < brand.length; end++) {
int temp = firstDp[end];
firstDp[end] = Math.max(brand[start] + secondDp[end],brand[end] + secondDp[end - 1]);
secondDp[end] = Math.min(temp, firstDp[end - 1]);
}
}
return Math.max(firstDp[brand.length - 1], secondDp[brand.length - 1]);
}
public static void main(String[] args) {
int[] arr = {1, 100, 300, 1, 366, 999, 456, 123, 0};
System.out.println(win1(arr));
System.out.println(win2(arr));
int[] a = {40,20,30,10};
System.out.println(win3(a));
System.out.println(win4(a));
}
}
空间压缩版本win4根据左神代码想通