题目

给定一个整型数组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("小伙子,你真棒!!!");
    }

}