题目
给定一个整型数组arr,代表数值不同的纸牌排成一条线
玩家A和玩家B依次拿走每张纸牌
规定玩家A先拿,玩家B后拿
但是每个玩家每次只能拿走最左或最右的纸牌
玩家A和玩家B都是绝顶聪明
请返回最后获胜者的分数
思路
方法一:普通递归,求一次就递归依次
//注意:
//first函数:当前玩家不管是谁,都是先手的身份。
//second函数:当前玩家不管是谁,都是后手的身份。
//first函数和second函数的先手后手不是固定的人,是一种当前玩家的身份!每个玩家都是轮流交换身份的!
public static int playOne(int[] arr){
if(arr == null || arr.length == 0){
return 0;
}
int N = arr.length;
//先手拿牌
int first = playOneFirst(arr, 0, N - 1);
//后手拿牌
int second = playOneSecond(arr, 0, N - 1);
return Math.max(first, second);
}
//先手拿牌
public static int playOneFirst(int[] arr, int L, int R){
if(L == R){ //只有一个,先手直接拿
return arr[L];
}
//先手如果拿 L,后手是L+1 - R中的后手,也就是L+1 - R中先手拿后的后手,此时先手,下一次就是后手
int ans1 = arr[L] + playOneSecond(arr, L+1, R);
//先手如果拿 R后手是L - R-1中的后手,也就是L - R-1中先手拿后的后手
int ans2 = arr[R] + playOneSecond(arr, L, R-1);
//先手取最大值
return Math.max(ans1, ans2);
}
//后手拿牌
public static int playOneSecond(int[] arr, int L, int R){
if(L == R){ //只有一个,后手就不给拿了哦
return 0;
}
//先手拿走了L位置的牌,后手从 L + 1 - R中找,相当于自己是先手,从L + 1 - R中找
int ans1 = playOneFirst(arr, L + 1, R);
//先手拿走了R位置的牌,后手冲 L - R-1中找,相当于自己是先手,从L - R-1中找
int ans2 = playOneFirst(arr, L, R - 1);
//既然是后手,肯定是先手拿走最大的,留给后手最小的
return Math.min(ans1, ans2);
}
方法二:傻缓存法,把已经求解的值用数组记录,下次遇到直接取,无效再递归
public static int playTwo(int[] arr){
if(arr == null || arr.length == 0){
return 0;
}
int N = arr.length;
//定义一个数组用来存储递归过程的中值,避免值的重复求解
int[][] fdp = new int[N][N]; //记录L - R范围中,先手拿到的值
int[][] sdp = new int[N][N]; //记录L - R范围中,后手拿到的值
//数组赋初值,不用默认0是因为,可能存在dp[i][j] = 0的情况
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
fdp[i][j] = -1;
sdp[i][j] = -1;
}
}
int ans1 = playTwoFirst(arr, 0, N - 1, fdp, sdp);
int ans2 = playTwoSecond(arr, 0, N - 1, fdp, sdp);
return Math.max(ans1, ans2);
}
public static int playTwoFirst(int[] arr, int L, int R, int[][] fdp, int[][] sdp){
if(fdp[L][R] != -1){
return fdp[L][R];
}
if(L == R){
fdp[L][R] = arr[L];
return fdp[L][R];
}
int ans1 = arr[L] + playTwoSecond(arr, L+1, R, fdp, sdp);
int ans2 = arr[R] + playTwoSecond(arr, L, R-1, fdp, sdp);
fdp[L][R] = Math.max(ans1,ans2);
return Math.max(ans1, ans2);
}
public static int playTwoSecond(int[] arr, int L, int R, int[][] fdp, int[][] sdp){
if(sdp[L][R] != -1){
return sdp[L][R];
}
if(L == R){
sdp[L][R] = 0;
return 0;
}
int ans1 = playTwoFirst(arr, L+1, R, fdp, sdp); //对手拿走了L位置的数
int ans2 = playTwoFirst(arr, L, R-1, fdp, sdp); //对手拿走了R位置的数
sdp[L][R] = Math.min(ans1, ans2);
return Math.min(ans1, ans2);
}
方法三:动态规划精髓-直接取值法
public static int playThree(int[] arr){
if(arr == null || arr.length == 0){
return 0;
}
int N = arr.length;
//定义一个数组用来存储递归过程的中值,避免值的重复求解
int[][] fdp = new int[N][N]; //记录L - R范围中,先手拿到的值
int[][] sdp = new int[N][N]; //记录L - R范围中,后手拿到的值
//把 R == L情况先写出来
for (int i = 0; i < N; i++) {
fdp[i][i] = arr[i]; //先手的 R == L 只有一个数
sdp[i][i] = 0; //后手的 R == L 不赋值则默认也是0
}
int index = 1; //用来判断循环次数的
while(index < N){ //循环N-1次即可
int i = 0; //横坐标
int j = index; //纵坐标
for (int k = 1; k <= N-index; k++) { //循环N次
fdp[i][j] = Math.max(arr[i] + sdp[i+1][j], arr[j] + sdp[i][j-1]);
sdp[i][j] = Math.min(fdp[i+1][j], fdp[i][j-1]);
i++;
j++;
}
index ++;
}
return Math.max(fdp[0][N-1], sdp[0][N-1]);
}
总结测试代码
public class PlayingCards {
//注意:
//first函数:当前玩家不管是谁,都是先手的身份。
//second函数:当前玩家不管是谁,都是后手的身份。
//first函数和second函数的先手后手不是固定的人,是一种当前玩家的身份!每个玩家都是轮流交换身份的!
public static int playOne(int[] arr){
if(arr == null || arr.length == 0){
return 0;
}
int N = arr.length;
//先手拿牌
int first = playOneFirst(arr, 0, N - 1);
//后手拿牌
int second = playOneSecond(arr, 0, N - 1);
return Math.max(first, second);
}
//先手拿牌
public static int playOneFirst(int[] arr, int L, int R){
if(L == R){ //只有一个,先手直接拿
return arr[L];
}
//先手如果拿 L,后手是L+1 - R中的后手,也就是L+1 - R中先手拿后的后手
int ans1 = arr[L] + playOneSecond(arr, L+1, R);
//先手如果拿 R后手是L - R-1中的后手,也就是L - R-1中先手拿后的后手
int ans2 = arr[R] + playOneSecond(arr, L, R-1);
//先手取最大值
return Math.max(ans1, ans2);
}
//后手拿牌
public static int playOneSecond(int[] arr, int L, int R){
if(L == R){ //只有一个,后手就不给拿了哦
return 0;
}
//先手拿走了L位置的牌,后手从 L + 1 - R中找,相当于自己是先手,从L + 1 - R中找
int ans1 = playOneFirst(arr, L + 1, R);
//先手拿走了R位置的牌,后手冲 L - R-1中找,相当于自己是先手,从L - R-1中找
int ans2 = playOneFirst(arr, L, R - 1);
//既然是后手,肯定是先手拿走最大的,留给后手最小的
return Math.min(ans1, ans2);
}
public static int playTwo(int[] arr){
if(arr == null || arr.length == 0){
return 0;
}
int N = arr.length;
//定义一个数组用来存储递归过程的中值,避免值的重复求解
int[][] fdp = new int[N][N]; //记录L - R范围中,先手拿到的值
int[][] sdp = new int[N][N]; //记录L - R范围中,后手拿到的值
//数组赋初值,不用默认0是因为,可能存在dp[i][j] = 0的情况
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
fdp[i][j] = -1;
sdp[i][j] = -1;
}
}
int ans1 = playTwoFirst(arr, 0, N - 1, fdp, sdp);
int ans2 = playTwoSecond(arr, 0, N - 1, fdp, sdp);
return Math.max(ans1, ans2);
}
public static int playTwoFirst(int[] arr, int L, int R, int[][] fdp, int[][] sdp){
if(fdp[L][R] != -1){
return fdp[L][R];
}
if(L == R){
fdp[L][R] = arr[L];
return fdp[L][R];
}
int ans1 = arr[L] + playTwoSecond(arr, L+1, R, fdp, sdp);
int ans2 = arr[R] + playTwoSecond(arr, L, R-1, fdp, sdp);
fdp[L][R] = Math.max(ans1,ans2);
return Math.max(ans1, ans2);
}
public static int playTwoSecond(int[] arr, int L, int R, int[][] fdp, int[][] sdp){
if(sdp[L][R] != -1){
return sdp[L][R];
}
if(L == R){
sdp[L][R] = 0;
return 0;
}
int ans1 = playTwoFirst(arr, L+1, R, fdp, sdp); //对手拿走了L位置的数
int ans2 = playTwoFirst(arr, L, R-1, fdp, sdp); //对手拿走了R位置的数
sdp[L][R] = Math.min(ans1, ans2);
return Math.min(ans1, ans2);
}
public static int playThree(int[] arr){
if(arr == null || arr.length == 0){
return 0;
}
int N = arr.length;
//定义一个数组用来存储递归过程的中值,避免值的重复求解
int[][] fdp = new int[N][N]; //记录L - R范围中,先手拿到的值
int[][] sdp = new int[N][N]; //记录L - R范围中,后手拿到的值
//把 R == L情况先写出来
for (int i = 0; i < N; i++) {
fdp[i][i] = arr[i]; //先手的 R == L 只有一个数
sdp[i][i] = 0; //后手的 R == L 不赋值则默认也是0
}
int index = 1; //用来判断循环次数的
while(index < N){ //循环N-1次即可
int i = 0; //横坐标
int j = index; //纵坐标
for (int k = 1; k <= N-index; k++) { //循环N次
fdp[i][j] = Math.max(arr[i] + sdp[i+1][j], arr[j] + sdp[i][j-1]);
sdp[i][j] = Math.min(fdp[i+1][j], fdp[i][j-1]);
i++;
j++;
}
index ++;
}
return Math.max(fdp[0][N-1], sdp[0][N-1]);
}
//生成数组
public static int[] generateArray(int maxSize, int maxValue){
int size = (int)((maxSize + 1) * Math.random());
int[] arr = new int[size];
for (int i = 0; i < size; i++) {
arr[i] = (int)((maxValue + 1) * Math.random());
}
return arr;
}
public static void main(String[] args) {
int testTime = 100;
int maxValue = 10;
int maxSize = 10;
for (int i = 0; i < testTime; i++) {
int[] arr = generateArray(maxSize, maxValue);
System.out.println(playOne(arr));
System.out.println(playTwo(arr));
System.out.println(playThree(arr));
if(playOne(arr) != playTwo(arr) || playOne(arr) != playThree(arr) || playTwo(arr) != playThree(arr)){
System.out.println("小伙子,这个答案有问题呀!!");
}
}
System.out.println("小伙子,你真棒!!!");
}
}