19.2:纸牌问题

给定一个整型数组arr,代表数值不同的纸牌排成一条线
玩家A和玩家B依次拿走每张纸牌
规定玩家A先拿,玩家B后拿
但是每个玩家每次只能拿走最左或最右的纸牌
玩家A和玩家B都绝顶聪明
请返回最后获胜者的分数

方法一:暴力解法

自然智慧。

package algorithmbasic.class19;

public class CardsInLine {

    public static int win2(int[] arr) {

        if (arr == null || arr.length < 2) {
            return -1;
        }

        int fresult = f2(arr, 0, arr.length - 1);

        int gresult = g2(arr, 0, arr.length - 1);

        return Math.max(fresult, gresult);
    }

    //作为先手函数,要在arr[L......R]范围上返回最好的成绩。
    public static int f2(int[] arr, int L, int R) {
        //如果现在只剩下一张纸牌了,作为先手直接拿走即可。
        if (L == R) {
            return arr[L];
        }
        //不止一张纸牌

        //如果我将最左侧的纸牌拿走,那接下来后手会怎么拿呢?然后就把自己当作后手来分析一下后手会如何做。
        int p1 = arr[L] + g2(arr, L + 1, R);

        //如果我将最右侧的纸牌拿走,那接下来后手会怎么拿呢?然后就把自己当作后手来分析一下后手会如何做。
        int p2 = arr[R] + g2(arr, L, R - 1);

        return Math.max(p1, p2);
    }

    private static int g2(int[] arr, int L, int R) {

        if (L == R) {
            return 0;
        }

        // 后手拿走最左侧的数,然后判断先手会在剩下的【L + 1, R】范围上的最优解。
        int p1 = f2(arr, L + 1, R);

        // 后手拿走最右侧的数,然后判断先手会在剩下的【L , R - 1】范围上的最优解。
        int p2 = f2(arr, L, R - 1);

        // p1 -> 先手在的【L + 1, R】范围上的最优解。
        // p2 -> 先手在的【L, R - 1】范围上的最优解。

        // 因为先手与后手都绝顶的聪明,所以最优解一定会被先手拿走。后手只能返回两者中最小的结果。

        //后手只能返回一个最小的,因为大的那个已经被先手拿走了。
        return Math.min(p1, p2);


    }

    // 给定数组arr之后,绝顶聪明的先手与后手的结果其实早已决定无力回天。 ----> 命运的安排
    // 于此同时主动权一直在先手这方。

}

方法二:记忆化搜索优化

暴力函数

	public static int win1(int[] arr) {

        int f = f1(arr, 0, arr.length - 1);

        int l = g1(arr, 0, arr.length - 1);

        return Math.max(f, l);
    }

    //返回先手在arr[L......R]范围上的最优分数。
    public static int f1(int[] arr, int L, int R) {

        if (L == R) {
            return arr[L];
        }

        int p1 = arr[L] + g1(arr, L + 1, R);

        int p2 = arr[R] + g1(arr, L, R - 1);

        return Math.max(p1, p2);
    }

    private static int g1(int[] arr, int L, int R) {

        if (L == R) {
            return 0;
        }

        int p1 = f1(arr, L + 1, R);

        int p2 = f1(arr, L, R - 1);

        return Math.min(p1, p2);
    }

1:分析原函数

在这里插入图片描述

发现加入缓存是合理的,因为真的出现重复计算了。

2:确定建立几张表以及表的范围

因为g方法与f方法都可能存在重复,所以建立两张表,表的范围是 [N] [N]

3:将表加入到原函数中。

// 方法二:记忆化搜索。
private static int win2(int[] arr) {

    int N = arr.length;

    int[][] fmap = new int[N][N];

    int[][] gmap = new int[N][N];

    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            fmap[i][j] = -1;
            gmap[i][j] = -1;
        }
    }

    int f = f2(arr, 0, arr.length - 1, fmap, gmap);

    int l = g2(arr, 0, arr.length - 1, fmap, gmap);

    return Math.max(f, l);
}

public static int f2(int[] arr, int L, int R, int[][] fmap, int[][] gmap) {

    if (fmap[L][R] != -1) {
        return fmap[L][R];
    }

    int ans = -1;

    if (L == R) {

        ans = arr[L];
    } else {

        int p1 = arr[L] + g2(arr, L + 1, R, fmap, gmap);

        int p2 = arr[R] + g2(arr, L, R - 1, fmap, gmap);

        ans = Math.max(p1, p2);
    }

    fmap[L][R] = ans;
    return ans;
}

private static int g2(int[] arr, int L, int R, int[][] fmap, int[][] gmap) {

    if (gmap[L][R] != -1) {
        return gmap[L][R];
    }
    int ans = -1;

    if (L == R) {
        ans = 0;
    } else {
        int p1 = f2(arr, L + 1, R, fmap, gmap);

        int p2 = f2(arr, L, R - 1, fmap, gmap);

        ans = Math.min(p1, p2);
    }

    gmap[L][R] = ans;

    return ans;
}

方法三:依赖版本迭代实现

1:分析依赖关系,确定表的大小

**分析依赖关系:**f 函数依赖g 函数,g 函数还会依赖 f 函数。

–> f 函数与g 函数中可能都存在重复值 --> 建两张表

确定表的大小关系:

public static int f1(int[] arr, int L, int R) {。。。。}
 private static int g1(int[] arr, int L, int R) {。。。。}

L的范围与R的范围不会超过数组的长度。

所以两张表的大小可以都是arr[arr.length] [arr.length]

2:根据原函数从上到下开始逐一分析

在这里插入图片描述

首先分析一下结果想要什么

	public static int win1(int[] arr) {

        int f = f1(arr, 0, arr.length - 1);

        int l = g1(arr, 0, arr.length - 1);

        return Math.max(f, l);
    }

发现结果想要的是f1图中的 (0, n-1 )位置。

以及g1图中的 (0, n-1 )位置。

分析f1方法

	public static int f1(int[] arr, int L, int R) {

        if (L == R) {
            return arr[L];
        }

        int p1 = arr[L] + g1(arr, L + 1, R);

        int p2 = arr[R] + g1(arr, L, R - 1);

        return Math.max(p1, p2);
    }

当L == R的时候,返回的是arr [L] - -> 直接在表中写出来。

当L != R时,即L < R时,会依赖 g1 方法。会依赖方法的那个位置呢?依赖的位置是:在f1图中的位置对应到g1图中的位置,

依赖的是在g1图中位置的左侧与下侧位置。与此同时,g1方法的依赖关系与f1一样。

3:根据原函数开始填表

原始函数

	public static int win1(int[] arr) {

        int f = f1(arr, 0, arr.length - 1);

        int l = g1(arr, 0, arr.length - 1);

        return Math.max(f, l);
    }

    //返回先手在arr[L......R]范围上的最优分数。
    public static int f1(int[] arr, int L, int R) {

        if (L == R) {
            return arr[L];
        }

        int p1 = arr[L] + g1(arr, L + 1, R);

        int p2 = arr[R] + g1(arr, L, R - 1);

        return Math.max(p1, p2);
    }

    private static int g1(int[] arr, int L, int R) {

        if (L == R) {
            return 0;
        }

        int p1 = f1(arr, L + 1, R);

        int p2 = f1(arr, L, R - 1);

        return Math.min(p1, p2);
    }

填表的最终结果如下:

private static int win3(int[] arr) {

        int N = arr.length;

        int[][] fmap = new int[N][N];

        int[][] gmap = new int[N][N];

        //根据f1中if (L == R) {return arr[L]; } 以及g1中的第一句话。
        for (int col = 0; col < N; col++) {
            fmap[col][col] = arr[col];
            gmap[col][col] = 0;
        }

        for (int start = 1; start < N; start++) {

            //斜着填
            int row = 0;
            int col = start;

            while(col < N) {
                fmap[row][col] = Math.max(arr[row] + gmap[row][col - 1], 
                                          arr[row] + gmap[row + 1][col]);
                gmap[row][col] = Math.min(fmap[row][col - 1], fmap[row + 1][col]);
                row++;
                col++;
            }
        }
        return Math.max(fmap[0][N - 1], gmap[0][N - 1]);
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

terrychen.hacker

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值