暴力递归转动态规划(二)

上一篇已经简单的介绍了暴力递归如何转动态规划,如果在暴力递归的过程中发现子过程中有重复解的情况,则证明这个暴力递归可以转化成动态规划。
这篇帖子会继续暴力递归转化动态规划的练习,这道题有点难度。

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

暴力递归
依然是先从暴力递归开始写起,一个先手拿,一个后手拿,两个人都绝顶聪明,都知道怎么拿可以利益最大化。
先手的拿完第一个之后,再拿的时候,就要从后手拿完的数组里再挑选了。
同理,如果后手的等先手的拿了之后,是不是就可以从剩余的数组里挑选最大利益的拿了。
依然先确定base case:
如果先手拿,最理想的状态就是当数组剩下最后一个数,依然可以被我拿走。
如果后手拿,最悲催的连数组最后一个数我都拿不到。
代码中f()函数是代表在数组L~ R范围上返回上先手拿能拿到的最大值返回。
g()函数代表在数组L ~ R范围上后手拿,能够获取的最大值。
需要注意的是身份的转变,如果先手拿之后,再拿的时候就会变成后手,第二个后手拿的时候,虽然我是后手,但是也是从数组中挑选利益最大的拿,留给先手拿的人的也是不好的,所以我会变成先手。

//先手方法
public static int f(int[] arr,int Lint R){//base case:先手拿,并且数组中剩一个元素,我拿走if(L == R){return arr[L];}//因为可以选择从左边拿和右边拿,从左边拿下一次就是L + 1开始,右边拿就是 R - 1 开始。//需要注意的是我从左或者从右拿完之后,再拿就是拿别人拿剩下的了,要以后手姿态获取其余分数,所以要调用g()方法int p1 = arr[L] + g(arr,L + 1,R);int p2 = arr[R] + g(arr, L, R -1);//两种决策中取最大值return Math.max(p1,p2);
}
//后手方法
public static int g(int[] arr,int L,int R){//剩最后一个也不是我的,毛都拿不到,return 0if(L == R){return 0;}//后手方法是在先手方法后,挑选最大值,那如果先手方法选择了L,则我要从L + 1位置选,//如果先手选择了R,那我要从R - 1位置开始往下选。//是从对手选择后再次选择最大值int p1 = f(arr,L + 1,R);int p2 = f(arr,L,R - 1);//因为是后手,是在先手后做决定,是被迫的,所以取Min。return Math.min(p1,p2);
}

先手后手方法已经确定,来看主流程怎么调用

public static int win1(int[] arr){//如果是无效数组,则返回一个无效数字 -1 if(arr == null || arr.length == 0){return -1;}int first = f(arr, 0 ,arr.length - 1);int second = g(arr,0,arr.length - 1);return Math.max(first,second);
}

暴力递归的分析和代码已经搞定,接下来我们通过分析暴力递归的调用过程来实现第一步的优化,找它的依赖,找它的重复解。
举一个具体的例子,arr[]范围 0~ 7,根据上面暴力递归的代码逻辑,我们来看看它的依赖关系和调用过程。如果确定了可变参数以及依赖关系,是不是就可以尝试着优化成动态规划。
在这里插入图片描述
根据代码逻辑,要么是取左边L + 1,要么是取右边 R - 1,所以可以确定可变参数是L和R,并且整个流程下来会发现有重复解的情况。
不过有些不同的是,这个是双层递归循环依赖调用,所以如果根据可变参数参数L,R来构建缓存表的话,则需要2个不同的缓存表分别记录。

优化
前面已经分析出整个暴力递归的调用过程,并发现了重复解,其中可变参数是L、R,根据L、R构建缓存表,因为是f()和g()的循环依赖调用,所以需要准备两张缓存表。

public static int win2(int[] arr) {if (arr == null || arr.length == 0) {return -1;}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 first = f1(arr, 0, arr.length - 1, fmap, gmap);int second = g1(arr, 0, arr.length - 1, fmap, gmap);return Math.max(first, second);}public static int f1(int[] arr, int L, int R, int[][] fmap, int[][] gmap) {// != -1,说明之前计算过该值,直接返回即可if (fmap[L][R] != -1) {return fmap[L][R];}int ans = 0;if (L == R){ans = arr[L];}else{int p1 = arr[L] + g1(arr, L + 1, R, fmap, gmap);int p2 = arr[R] + g1(arr, L, R - 1, fmap, gmap);ans = Math.max(p1, p2);}//这一步能够取得的最大值fmap[L][R] = ans;return ans;}public static int g1(int[] arr, int L, int R, int[][] fmap, int[][] gmap) {if (gmap[L][R] != -1){return gmap[L][R];}//因为如果 L == R,后手方法会返回0,默认ans也是等于0,省略一步判断int ans = 0;if (L != R){int p1 = f1(arr,L + 1,R,fmap,gmap);int p2 = f1(arr,L,R - 1,fmap,gmap);ans = Math.min(p1,p2);}gmap[L][R] = ans;return ans;}

二次优化
我们上面已经创建了缓存表,并找到了变量L、R,我们现在不妨举一个例子,并将缓存表画出来,来看一下表中每一列的对应关系,如果我们能找到这个缓存表的对应关系,是不是将表构建出来以后,就可以直接获取获胜者的最大值。
在这里插入图片描述
数组arr = {7,4,16,15,1} 因为有两张缓存表,所以需要将两张表的依赖关系都找出。接下来,回到最开始的暴力递归方法,根据代码逻辑一步一步找出依赖关系。

public static int win1(int[] arr) {if (arr == null || arr.length == 0) {return -1;}int first = f(arr, 0, arr.length - 1);int second = g(arr, 0, arr.length - 1);return Math.max(first, second);}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);}public static int g(int[] arr, int L, int R) {if (L == R) {return 0;}int p1 = f(arr, L + 1, R);int p2 = f(arr, L, R - 1);return Math.min(p1, p2);}

从先手方法f()和后手方法g()的base case可以看出,如果当L == R时,f()方法中此时就是等于数组arr[L]本身的值,而g()中为0,又因为,每次我只选L或只选R,当L = R时就return了,所以我的L始终不会 > R。我们所要求的L ~ R 范围是整个数组0 ~ 4的值,此时图可以填充成这样。
在这里插入图片描述
再来接着往下看,如果此时LR随便给一个值,比如说当前fmap中L = 1,R = 3,来接着看它的依赖过程。
在这里插入图片描述
根据代码可以看出,它依赖的是g()方法中L +1和R - 1,所以对应在gmap中的依赖就是圆圈标记的部分。对应的,同样 L = 1 R = 3在gmap中也是依赖fmap对应的位置。
在这里插入图片描述
那现在有缓存表中每个位置的依赖关系,还有fmap和gmap当L == R时的值,是不是就可以推算出其他格子中的值。

代码

 public static int win3(int[] arr) {if (arr == null || arr.length == 0) {return -1;}int N = arr.length;int[][] fmap = new int[N][N];int[][] gmap = new int[N][N];//根据base  case填充fmap,gmap都是0,数组初始化值也是0,不用填充for (int i = 0; i < N; i++) {fmap[i][i] = arr[i];}//根据对角线填充,从第一列开始for (int startCol = 1; startCol < N; startCol++) {int L = 0;int R = startCol;while (R < N) {//将调用的g()和f()都替换成对应的缓存表fmap[L][R] = Math.max(arr[L] + gmap[L + 1][R], arr[R] + gmap[L][R - 1]);gmap[L][R] = Math.min(fmap[L + 1][R], fmap[L][R - 1]);L++;R++;}}//最后从L ~ R位置,取最大值return Math.max(fmap[0][N -1],gmap[0][N-1]);}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/58473.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用心维护好电脑,提高学习工作效率

文章目录 一、我的电脑1.1 如何查看自己的电脑硬件信息呢&#xff1f; 二、电脑标准保养步骤和建议2.1 保持清洁2.2 定期升级系统和软件2.3 安全防护2.4 清理磁盘空间2.5 备份重要数据2.6 优化启动项2.7 散热管理2.8 硬件维护2.9 电源管理2.10 注意下载和安装2.11 定期维护 三、…

Web开发

什么是Web&#xff1f; Web&#xff1a;全球广域网&#xff0c;也称万维网&#xff08;www World Wide Web&#xff09;&#xff0c;能够通过浏览器访问的网站 Web网站的工作流程&#xff1a; 浏览器------&#xff08;请求&#xff09;--------前端服务器------&#xff08;相…

C++语法基础

这里写目录标题 基础语法第一个程序变量常量的定义关键字标识符命名 &#xff08;变量命名&#xff09;sizeof的使用实型&#xff08;浮点型&#xff09;字符型转义字符字符串的定义 一级目录二级目录二级目录二级目录 一级目录二级目录二级目录二级目录 基础语法 第一个程序 …

python编程中fft的优缺点,以及如何使用cuda编程,cuda并行运算,信号处理(推荐)

A.python中cuda编程的库主要有: cupy、pycuda 1,区别如下: 支持的GPU平台: PyCUDA:PyCUDA是一个用于在Python中编写CUDA代码的库。它支持NVIDIA的CUDA平台,并提供了与CUDA C/C++接口相似的功能。因此,PyCUDA主要用于与NVIDIA GPU交互的应用。 CuPy:CuPy是一个用于在P…

研究发现,可以很随意地破解破越狱ChatGPT、Bard和Claude的安全限制措施

最新研究发现&#xff0c;可以很随意地破解ChatGPT、Bard和Claude等主流AI系统的安全限制措施。 卡内基梅隆大学和旧金山AI安全中心的研究人员在最新报告中表示&#xff0c;他们已经发现了潜在的多种方法来突破主流AI聊天机器人的安全限制。 ChatGPT、Bard和Claude等语言模型…

用 PHP 和 JavaScript 显示地球卫星照片

向日葵 8 号气象卫星是日本宇宙航空研究开发机构设计制造的向日葵系列卫星之一&#xff0c;重约 3500 公斤&#xff0c;设计寿命 15 年以上。该卫星于 2014 年 10 月 7 日由 H2A 火箭搭载发射成功&#xff0c;主要用于监测暴雨云团、台风动向以及持续喷发活动的火山等防灾领域。…

hadoop 学习:mapreduce 入门案例一:WordCount 统计一个文本中单词的个数

一 需求 这个案例的需求很简单 现在这里有一个文本wordcount.txt&#xff0c;内容如下 现要求你使用 mapreduce 框架统计每个单词的出现个数 这样一个案例虽然简单但可以让新学习大数据的同学熟悉 mapreduce 框架 二 准备工作 &#xff08;1&#xff09;创建一个 maven 工…

Win11共享文件,能发现主机但无法访问,提示找不到网络路径

加密长度选择如下&#xff1a; 参考以下链接&#xff1a; Redirectinghttps://answers.microsoft.com/zh-hans/windows/forum/all/win11%E8%AE%BE%E7%BD%AE%E6%96%87%E4%BB%B6%E5%A4%B9/554343a9-d963-449a-aa59-ce1e6f7c8982?tabAllReplies#tabs

LCS(最长公共子序列)

滑动窗口处理&#xff0c;一直使用str2判断str1的其中一段&#xff0c;并且要判断sb和r-l的大小 public static String LCS(String str1,String str2){StringBuilder sb new StringBuilder();int l 0;int r 1;while(r<str1.length()1){if (str2.contains(str1.substring…

JS类型判断的那些方法

目前类型判断有三种方法&#xff1a;typeof 、instanceof 、Object.prototype.toString.call typeof&#xff1a;只能判断基础类型和函数类型 typeof 2 // number typeof true // boolen typeof str // string typeof function(){} // function …

StarRocks入门到熟悉

1、部署 1.1、注意事项 需要根据业务需求设计严谨的集群架构&#xff0c;一般来说&#xff0c;需要注意以下几项&#xff1a; 1.1.1、FE数量及高可用 FE的Follower要求为奇数个&#xff0c;且并不建议部署太多&#xff0c;通常我们推荐部署1个或3个Follower。在三个Followe…

STM32驱动SD卡(SPI)方式

外观 代码(免费分享) 接线 5V供电 CS接PA3 剩下如图按照硬件SPI1接线 注意事项 使用杜邦线接线非常不稳定&#xff01;&#xff01;&#xff01; 使用杜邦线接线非常不稳定&#xff01;&#xff01;&#xff01; 使用杜邦线接线非常不稳定&#xff01;&#xff01;&#…

将Spring Boot与Redis集成

一、引言 1、SpringBoot&#xff1a; Spring Boot是一个用于创建独立且可执行的Spring应用程序的框架。它简化了基于Spring框架的应用程序的开发过程&#xff0c;并提供了一种快速和简便的方式来构建Java应用程序。 Spring Boot提供了自动配置机制&#xff0c;通过引入适当的…

图的存储:十字链表,邻接多重表

1.十字链表存储有向图 1.存储方式 分为顶点结点和弧结点两种结构体 顶点结点使用数组顺序存储&#xff0c;结构体包括&#xff1a;数据域&#xff0c;作为顶点弧头的第一条弧&#xff0c;作为顶点弧尾的第一条弧。 弧结点&#xff0c;结构体包括&#xff1a;弧头相同的下一…

Python中pip和conda的爱恨情仇

在使用pip和conda时&#xff0c;是否也有过以下的疑惑&#xff1f;&#xff1f;&#xff1f; 目前只总结了以下常见的几种混淆&#xff0c;如有学者还有其它疑惑&#xff0c;欢迎留言讨论&#xff0c;我会解答更新&#xff0c;帮助自己理清的同时&#xff0c;也帮助其他同样困…

从零开始学JAVA——常用类

常用类 课后习题一&#xff1a;课后练习二&#xff1a;课后练习三&#xff1a;课后练习四课后练习五&#xff1a; 课后习题一&#xff1a; 将字符串“2016-02-22”转换为对应的java.sql.Date类的对象 SimpDateFormat sdf new SimpDateFormate(“yyyy-MM-DD”); 解析 java.ut…

机械臂+2d相机实现复合机器人定位抓取

硬件参数 机械臂&#xff1a;艾利特 相机&#xff1a;海康相机 2d识别库&#xff1a;lindmod&#xff0c;github可以搜到 光源&#xff1a;磐鑫光源 软件参数 系统&#xff1a;windows / Linux 开发平台&#xff1a;Qt 开发语言&#xff1a;C 开发视觉库&#xff1a;OpenCV …

nlp系列(7)三元组识别(Bert+CRF)pytorch

模型介绍 在实体识别中&#xff1a;使用了Bert模型&#xff0c;CRF模型 在关系识别中&#xff1a;使用了Bert模型的输出与实体掩码&#xff0c;进行一系列变化&#xff0c;得到关系 Bert模型介绍可以查看这篇文章&#xff1a;nlp系列&#xff08;2&#xff09;文本分类&…

gitlab-runner安装和部署项目

目录 1.安装gitlab-runner 1.1 添加官方仓库 1.2.1 安装最新版本 1.2.2 安装指定版本&#xff08;可选&#xff09; 1.2.3 更新runner&#xff08;可选&#xff09; 1.3 随便点开gitlab上的一个项目 1.4 gitlab-runner的注册 2.配置gitlab-runner 3.runner一些命令 gi…

通达OAV12版本,表单及流程,定制开发总结

通达OA-V12版本&#xff0c;表单及流程&#xff0c;定制开发总结 触发器金蝶系统对接 日期&#xff1a;2023年8月29日 触发器 一键转交操作&#xff0c;不会调用触发器。 解决办法&#xff1a;可以按需要按步骤&#xff0c;关闭一键转交按钮。这里会隐藏一键转交、一键结束按钮…