首页 > 其他分享 >纸牌博弈问题

纸牌博弈问题

时间:2022-11-01 19:44:43浏览次数:77  
标签:firstMap end 纸牌 int return 问题 start 博弈 Math

纸牌博弈问题

作者:Grey

原文地址:

博客园:纸牌博弈问题

CSDN:纸牌博弈问题

题目描述

有一个整型数组 A,代表数值不同的纸牌排成一条线。玩家 a 和玩家 b 依次拿走每张纸牌,
规定玩家 a 先拿,玩家 b 后拿,
但是每个玩家每次只能拿走最左或最右的纸牌,
玩家 a 和玩家 b 都绝顶聪明,他们总会采用最优策略。
请返回最后获胜者的分数。

注:给定纸牌序列 A 及序列的大小 n,请返回最后分数较高者得分数(相同则返回任意一个分数)。保证 A 中的元素均小于等于1000。且 A 的大小小于等于300。

题目链接:牛客-纸牌博弈

暴力递归解

定义两个递归函数,第一个递归函数是

int first(int[] A, int n, int start, int end)

这个递归函数表示的含义是:先手在数组 A 的 start 到 end 范围内,经过 n 轮,能获得的最大的分数是多少。

base case 是,当 start == end 的时候,即数组 A 只有一个元素,此时先手直接拿走这个元素,最大分数就是此时先手拿走的唯一元素值,即

        if (start == end) {
            return A[start];
        }

第二个递归函数是

int second(int[] A, int n, int start, int end)

这个递归函数表示的含义是:后手在数组 A 的 start 到 end 范围内,经过 n 轮,能获得的最大的分数是多少。

base case 是,当 start == end 的时候,即数组 A 只有一个元素,此时这个元素肯定要被先手拿走,那么后手只能返回 0,即

        if (start == end) {
            return 0;
        }

接下来是普遍情况,对于先手函数来说,有两个位置可以选,一个是 start 位置,另外一个是 end 位置,如果选了 start 位置,那么先手在下一轮就是后手,即

A[start] + second(A, n, start + 1, end)

同理,如果选 end 位置,先手在下一轮是后手,即

A[end] + second(A, n, start, end - 1)

先手函数在做上述两个决策的过程中,一定要取最大值,即

Math.max(A[start] + second(A, n, start + 1, end), A[end] + second(A, n, start, end - 1));

所以,先手函数的完整代码如下

    public static int first(int[] A, int n, int start, int end) {
        if (start == end) {
            return A[start];
        }
        return Math.max(A[start] + second(A, n, start + 1, end), A[end] + second(A, n, start, end - 1));
    }

接下来是后手函数的普遍情况,对于后手函数来说,没有先选的权力,只能让先手选完自己才能选,先手如果选了 start 位置,后手面对的选择是

first(A, n, start + 1, end)

先手如果选了 end 位置,后手面对的选择就是

first(A, n, start, end - 1)

后手在下一轮就是先手,所以要保证先手的上述选择最小,即

Math.min(first(A, n, start + 1, end), first(A, n, start, end - 1));

定义了先手函数和后手函数,主函数做如下调用即可

    public static int cardGame(int[] A, int n) {
        // 没有元素,直接返回0分
        if (n == 0) {
            return 0;
        }
        // 只有一个元素,无论如何,只能得到 A[0] 分
        if (n == 1) {
            return A[0];
        }
        // 只有两个元素,选最大的那个
        if (n == 2) {
            return Math.max(A[0], A[1]);
        }
        // 普遍情况:先手后手中最大的那个
        return Math.max(first(A, n, 0, A.length - 1), second(A, n, 0, A.length - 1));
    }

超时

image

动态规划解

根据上述暴力递归函数可知,两个递归函数都可变参数都是 2 个,且可变参数的变化范围是固定的,所以我们可以用两个二维数组来分别表示两个递归函数的结果,

// 保存先手函数的递归过程解
int[][] firstMap = new int[n][n];
// 保存后手函数的递归过程解
int[][] secondMap = new int[n][n];

由于递归过程的两个可变参数 start 和 end 是有范围的,且 start 不可能大于 end,所以,上述两个二维数组的左下半区都是无效区域,无需考虑。

在暴力递归过程中,当 start == end 的时候,返回 A[start] 值,所以,针对 firstMap,其对角线(start == end)的值都是确定的

        // 对角线
        for (int i = 0; i < n; i++) {
            firstMap[i][i] = A[i];
        }

经过上述过程,可以得到两个二维数组的如下区域的内容

image

接下来就是普遍情况,

基于暴力递归过程可以很方便得到两个二维数组之间的递推关系

        // 对角线下班区域不用管
        // 对角线上半区域
        for (int i = 1; i < n; i++) {
            int r = 0;
            int c = i;
            while (c < n) {
                firstMap[r][c] = Math.max(A[r] + secondMap[r + 1][c], A[c] + secondMap[r][c - 1]);
                secondMap[r][c] = Math.min(firstMap[r + 1][c], firstMap[r][c - 1]);
                r++;
                c++;
            }
        }

完整代码如下

import java.util.*;

public class Cards {

    public static int cardGame(int[] A, int n) {
        if (n == 0) {
            return 0;
        }
        if (n == 1) {
            return A[0];
        }
        if (n == 2) {
            return Math.max(A[0], A[1]);
        }
        int[][] firstMap = new int[n][n];
        int[][] secondMap = new int[n][n];
        // 对角线
        for (int i = 0; i < n; i++) {
            firstMap[i][i] = A[i];
        }
        // 对角线下班区域不用管
        // 对角线上半区域
        for (int i = 1; i < n; i++) {
            int r = 0;
            int c = i;
            while (c < n) {
                firstMap[r][c] = Math.max(A[r] + secondMap[r + 1][c], A[c] + secondMap[r][c - 1]);
                secondMap[r][c] = Math.min(firstMap[r + 1][c], firstMap[r][c - 1]);
                r++;
                c++;
            }
        }
        return Math.max(firstMap[0][n - 1], secondMap[0][n - 1]);
    }
}

更多

算法和数据结构笔记

标签:firstMap,end,纸牌,int,return,问题,start,博弈,Math
From: https://www.cnblogs.com/greyzeng/p/16848911.html

相关文章

  • 在Vue中使用Swiper轮播图、同时解决点击轮播图左右切换按钮不生效的问题、同时将轮播
    轮播图左右的切换按钮、如果点击没有反应,控制台也没有报错。很大可能是==版本问题==。如果不指定版本信息、默认安装的是最新的版本。版本过高或者过低都有可能导致无效。......
  • iOS上拉边界下拉白色空白问题解决概述
    表现手指按住屏幕下拉,屏幕顶部会多出一块白色区域。手指按住屏幕上拉,底部多出一块白色区域。产生原因在iOS中,手指按住屏幕上下拖动,会触发 touchmove 事件。这个事......
  • Windows下Tomcat内存占用过高问题跟踪(jmap 的使用)
    一、问题描述Tomcat下面部署很多个java项目的war包,tomcat启动一段时间后,发现cpu占用过高,整个界面卡死!二、通过tasklist命令查看java进程下的线程三、通过jstack把进程下......
  • 传统的IO存在什么问题?为什么引入零拷贝的?
    传统的IO存在什么问题?为什么引入零拷贝的?如果服务端要提供文件传输的功能,我们能想到的最简单的方式是:将磁盘上的文件读取出来,然后通过网络协议发送给客户端。传统I/O的工......
  • 网页设计制作要注意哪些问题
    网页设计制作是UI设计师工作内容的主要组成部分,现在网页设计制作中比较常见的图片一般会有三种类型:logo、照片和插图。不同类型的图片就会有自己的特色,在进行设计的时候......
  • react项目因为代码复杂度问题无法打包
    项目中碰到个问题,后台返回数据为null,但是之前代码没有做null的判断,导致使用该数据里属性值时报错  很快,在代码中定位到报错字段,加上可选链操作符( ?.)时,代码编译运......
  • DJango + Vue 跨域问题解决
    什么是跨域同源:协议+域名+端口号,三者完全相同以上三个元素只要有一个不相同就是跨域产生跨域异常的报错信息如下:accesstoxmlhttprequestat'http://ip:port1/a......
  • PCB设计很简单?生产问题才是考验工程师能力的标准!
    BOM清单有误SMT产线:物料封装怎么和PCB焊盘不一致呢?停线排查。仓库:我是按照BOM清单发的物料。硬件研发:哎,BOM整理时马虎了。过孔焊盘问题SMT产线:怎么又把过孔打在焊盘上了?回......
  • go mongo 时区问题
       https://zhuanlan.zhihu.com/p/479189070   funcmain(){sh,err:=time.LoadLocation("Asia/Shanghai")//设置时区iferr!=nil{......
  • 关于Editview失去焦点问题
    项目背景:在一次项目中,需要输入框输入结束后就立即发送注册包给平台以获取网关列表,开始的想法是,监听收起键盘就执行,但是最后没有实现,最后想到了editview失去焦点的事件,一旦......