算法练习:从暴力递归到动态规划2

本文介绍了一种纸牌游戏的最优策略计算方法,通过暴力递归和动态规划两种方式求解玩家A和玩家B拿牌后的最终得分,并给出具体实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

题目二

暴力递归:

动态规划:


题目二

给定一个整型数组arr,代表数值不同的纸牌排成一条线玩家A和玩家B依次拿走每张纸牌

规定玩家A先拿,玩家B后拿

但是每个玩家每次只能拿走最左或最右的纸牌玩家A和玩家B都绝顶聪明

请返回最后获胜者的分数。

暴力递归:

思路:由于最后要比较玩家A和玩家B的分数大小,所以要返回两个玩家的分数,因此假定先手拿牌返回分数的方法为 f();后手拿牌返回分数的方法为 g()。

假定数组最左边索引为L,最右边索引为R。

对于 f():

  • 当 L == R 时,仅剩最后一张牌,直接返回L;

  • 当先手拿L时,等同于下一次后手在(L+1,R)中拿一张;当先手拿R时,等同于下一次后手在(L,R-1)中拿一张;累加;返回两种情况中的最大值。

对于 g():

  • 当 L == R时,仅剩最后一张牌,由于是后手拿,最后一张牌会被前面的人拿走,所以返回0;

  • 当L被拿走时,等同于这次先手在(L+1,R)中拿一张;当R被拿走时,等同于这次先手在(L,R-1)中拿一张;返回两种情况中的最小值。 注:因为两人绝顶聪明,因此先手的人总会把两种方法中所剩牌累加最小的方法留给后手的人,因此返回最小值。

代码如下:

	//纸牌游戏(暴力递归到动态规划)
	//主方法
    public static int MainWay (int[] arr) {
        if (arr == null || arr.length == 0) {
            return 0;
        }
        int L = 0;
        int R = arr.length-1;
        int first = f(arr,L,R);
        int second = g(arr,L,R);
        return Math.max(first,second);
    }
	//先手拿牌 f()方法
    public static int f(int[] arr, int L, int R) {
        if (L == R) {
            return arr[L];
        }
        int p1 = arr[L] + g(arr,L+1,R); //若先手拿左边
        int p2 = arr[R] + g(arr,L,R-1); //若先手拿右边
        return Math.max(p1,p2);
    }
    //后手拿牌 g()方法
    public static int g(int[] arr, int L, int R) {
        if (L == R) {
            return 0;
        }
        int p1 = f(arr,L+1,R); //L被先手拿走
        int p2 = f(arr,L,R-1); //R被先手拿走
        return Math.min(p1,p2);
    }

输出如下:

	public static void main(String[] args) {
        int[] arr = {5,7,4,5,8,1,6,0,3,4,6,1,7};
        System.out.println(MainWay(arr));
    }
    //输出结果:
    //32

动态规划:

分析可知:暴力递归的过程中存在重复的运算,因此我们可以对其进行优化。

  • 优化的方法是根据L、R两个变量建立"二维缓存表";

  • 对"二维缓存表赋值";这里用两张表来对应 f()与 g();

  • 在从暴力递归到动态规划的过程中,暴力递归中的算法往往是建立"缓存表"的关键。我们通过暴力递归中的算法对"缓存表"进行填表赋值。

简而言之:暴力递归的过程是动态规划时给表赋值的方法。

	//动态规划:
    //根据暴力递归建立个“L-R 二维缓冲表”,通过二维表返回较大分数的一方
    public static int way2 (int[] arr, int L, int R) {
        if (arr == null || arr.length == 0) {
            return 0;
        }
        int N = arr.length;
        //创建二维表
        int[][] fmap = new int[N][N]; //f()的L-R二维表
        int[][] gmap = new int[N][N]; //g()的L-R二维表
        //填表
        for (int i = 0; i < N; i++) {
            fmap[i][i] = arr[i]; //先手方L==R时的表赋值
        }
        //L为行;R为列
        for (int mapR = 1; mapR < N; mapR++) {
            int L1 = 0;
            int R1 = mapR;
            while (R1 < N) {
                fmap[L1][R1] = Math.max(arr[L1] + gmap[L1+1][R1], arr[R1]
                                        + gmap[L1][R1-1]);
                gmap[L1][R1] = Math.min(fmap[L1+1][R1], fmap[L1][R1-1]);
                L1++;
                R1++;
            }
        }
        return Math.max(fmap[L][R],gmap[L][R]);//返回分数较大的一方
    }

输出结果如下:

public static void main(String[] args) {
        int[] ints = {4,2,66,33,44};
        int[] arr = {5,7,4,5,8,1,6,0,3,4,6,1,7};
        
        System.out.println(MainWay(arr));
        System.out.println(way2(arr,0,arr.length-1));
        
        System.out.println(MainWay(ints));
        System.out.println(way2(ints,0, ints.length-1));
    }
    //输出:
    32
    32
    79
    79

上一期文章链接:算法练习:从暴力递归到动态规划1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

superzheng__

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

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

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

打赏作者

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

抵扣说明:

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

余额充值