/**
* @version 0.1
* @since 2021/10/23
* @author Void Bug
*
*
* <br>问题描述<br>
* 有一排正数,玩家A和玩家B都可以看到。
* 每位玩家在拿走数字的时候,都只能从最左和最右的数中选择一个。
* 玩家A先拿,玩家B再拿,两人交替拿走所有的数字,
* 两人都力争自己拿到的数的总和比对方多。请返回最后获胜者的分数。
* 例如:
* 5,2,3,4
* 玩家A先拿,当前他只能拿走5或者4。
* 如果玩家A拿走5,那么剩下2,3,4。轮到玩家B,此时玩家B可以选择2或4中的一个,…
* 如果玩家A拿走4,那么剩下5,2,3。轮到玩家B,此时玩家B可以选择5或3中的一个,…
*/
public class Problem_03_CardsInLine {
/**
* win1():由于存在AB玩家都可以看到数字的大小,所以每次都在做一个决策。先开始的玩家势必会拿最大数,而第二玩家势必只能在第一个玩家拿到最大的前提下,自己去拿剩下数字中的最大者。所以用递归就可以很清晰的表达。
* 针对谁先拿必然是先拿最大者,而后者就是拿最次大者。依序就是 first->second->first—>second …
* a.其中 当i == j 对于first来说 可以拿到arr[i]
* b.i == j 对于second来说 拿不到arr[i] 因为first具有优先权。
* @param array 传入的 array 数值
* @return 返回我最后获胜者最大分数
*/
public static int win1(int[] array) {
if (array == null || array.length == 0) {
return 0;
}
return Math.max(first(array, 0, array.length - 1), second(array, 0, array.length - 1));
}
/** 先手暴力递归得到从 0 ~ n-1的最大值
* 先手时的最大获胜者最大分数
* @param array 传入的 array 数值
* @param leftIndex 左下标
* @param rightIndex 右下标
* @return 先手暴力递归得到从 0 ~ n-1的最大值
*/
private static int first(int[] array, int leftIndex, int rightIndex) {
if (leftIndex == rightIndex) {
return array[leftIndex];
}
return Math.max(array[leftIndex] + second(array, leftIndex + 1, rightIndex), array[rightIndex] + second(array, leftIndex, rightIndex - 1));
}
/** 后手暴力递归得到从 0 ~ n-1的最大值
* 后手时的最大获胜者最大分数
* @param array 传入的 array 数值
* @param leftIndex 左下标
* @param rightIndex 右下标
* @return 后手暴力递归得到从 0 ~ n-1的最大值
*/
private static int second(int[] array, int leftIndex, int rightIndex) {
if (leftIndex == rightIndex) {
return 0;
}
return Math.min(first(array, leftIndex + 1, rightIndex), first(array, leftIndex, rightIndex - 1));
}
/**
* win2()思路
* win1中递归会存在大量的重复计算。因此我们可以改成dp。dp主要是基于已知的条件来对下一项进行选择。而first玩家的决策,会影响到second玩家,
* 因此。需要二维dp来解决这个问题。first[][] 记录first玩家的决策路径,second[][]记录second玩家的决策路径。i记录行号,j记录列。i的值不可能大于j 所以左下三角是没有数据的。
* @param array 传入的 array 数值
* @return 返回我最后获胜者最大分数
*/
public static int win2(int[] array) {
if (array == null || array.length == 0) {
return 0;
}
int[][] first = new int[array.length][array.length];
int[][] second = new int[array.length][array.length];
for (int j = 0; j < array.length; j++) {
first[j][j] = array[j];
for (int i = j - 1; i >= 0; i--) {
first[i][j] = Math.max(array[i] + second[i + 1][j], array[j] + second[i][j - 1]);
second[i][j] = Math.min(first[i + 1][j], first[i][j - 1]);
}
}
return Math.max(first[0][array.length - 1], second[0][array.length - 1]);
}
/**
* win3的思路是借鉴win1 是基于win1来说的,win1中需要计算first和second的两个值。如果这样想 只计算一个值value,总数sum-value 只需要比较两者最大值就可以了。
* @param array 传入的 array 数值
* @return 返回我最后获胜者最大分数
*/
public static int win3(int[] array) {
if (array == null || array.length == 0) {
return 0;
}
int scores = getMaxSum(array, 0, array.length - 1);
return Math.max(getSumValue(array) - scores, scores);
}
/**
* 返回 Array 的 总和
* @param array 传入的 array 数值
* @return 返回 Array 的 总和
*/
private static int getSumValue(int[] array){
int sum = 0;
for (int j : array) {
sum += j;
}
return sum;
}
/** 先手 动态规划递归 得到从 0 ~ n-1的最大值
* 先手时的最大获胜者最大分数
* @param array 传入的 array 数值
* @param leftIndex 左下标
* @param rightIndex 右下标
* @return 先手暴力递归得到从 0 ~ n-1的最大值
*/
private static int getMaxSum(int[] array, int leftIndex, int rightIndex) {
if (leftIndex == rightIndex) {
return array[leftIndex];
}
if (leftIndex + 1 == rightIndex) {
return Math.max(array[leftIndex], array[rightIndex]);
}
return Math.max(
array[leftIndex] + Math.min(getMaxSum(array, leftIndex + 2, rightIndex), getMaxSum(array, leftIndex + 1, rightIndex - 1)),
array[rightIndex] + Math.min(getMaxSum(array, leftIndex + 1, rightIndex - 1), getMaxSum(array, leftIndex, rightIndex - 2))
);
}
/**
* win4的思路是借鉴win3的方法,及将 win3 改成动态规划的方法解决问题
* @param array 传入的 array 数值
* @return 返回我最后获胜者最大分数
*/
public static int win4(int[] array) {
if (array == null || array.length == 0) {
return 0;
}
if (array.length == 1) {
return array[0];
}
if (array.length == 2) {
return Math.max(array[0], array[1]);
}
// 初始化对角线及其对角线往上的那一条线的值, array[n][n] 这个除外,如果 array[n][n] 这个在这里赋值的话,则 dp[n][n + 1] 就报错的,故单独赋值
int[][] dp = new int[array.length][array.length];
for (int i = 0; i < array.length - 1; i++) {
dp[i][i] = array[i];
dp[i][i + 1] = Math.max(array[i], array[i + 1]);
}
dp[array.length - 1][array.length - 1] = array[array.length - 1];
for (int k = 2; k < array.length; k++) {
for (int j = k; j < array.length; j++) {
int i = j - k;
dp[i][j] = Math.max(
array[i] + Math.min(dp[i + 2][j], dp[i + 1][j - 1]),
array[j] + Math.min(dp[i + 1][j - 1], dp[i][j - 2])
);
}
}
return Math.max(dp[0][array.length - 1], getSumValue(array) - dp[0][array.length - 1]);
}
/**
* 数组的对数器:生成随机数组,不定长不定值正整型数组
* @return 返回生成的数组
*/
public static int[] getRandomArray() {
int[] res = new int[(int) (Math.random() * 20) + 1];
for (int i = 0; i < res.length; i++) {
res[i] = (int) (Math.random() * 20) + 1;
}
return res;
}
/**
* 主方法
* @param args 输入的信息
* 测试方法
*/
public static void main(String[] args) {
int testTime = 500;
boolean err = false;
for (int i = 0; i < testTime; i++) {
int[] arr = getRandomArray();
int r1 = win1(arr);
int r2 = win2(arr);
int r3 = win3(arr);
int r4 = win4(arr);
if (r1 != r2 || r1 != r3 || r1 != r4) {
err = true;
}
}
if (err) {
System.out.println("3333333333");
} else {
System.out.println("6666666666");
}
}
}
算法学习(31)- 两人抽卡问题最大分数问题
猜你喜欢
转载自blog.csdn.net/qq_45205390/article/details/120951836
今日推荐
周排行