拿牌游戏--暴力递归转动态规划2

题目:给定一个整型数组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根据左神代码想通

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值