暴力递归转动态规划(十一)

题目1:
这篇帖子中有多道题,由浅入深。
arr是货币数组,其中的值都是正数。再给定一个正数aim。每个值都认为是一张货币,即便是值相同的货币也认为每一张都是不同的,返回组成aim的方法数。
例如:arr = {1,1,1},aim = 2
第0个和第1个能组成2,第1个和第2个能组成2,第0个和第2个能组成2
一共就3种方法,所以返回3

暴力递归
这道题相对来讲比较基础,很简单的从左往右尝试模型,给定的arr数组中,从左向右依次尝试,每个arr[index]一共就2种情况:要 和 不要。
并且确定好base case(何时终止递归):
第一种情况就是当index = arr.length时, 我的数组已经到尾了,取不出来东西了。
第二种是如果使用了当前arr[index]处的值,则用aim - arr[index]后,用rest(剩余钱数)向下传递,如果rest 为 0时,说明正好凑够了这个钱,也return。

代码

 public static int coinWays(int[] arr, int aim) {return process(arr, 0, aim);}public static int process(int[] arr, int index, int rest) {if (rest < 0) {return 0;}if (index == arr.length) {return rest == 0 ? 1 : 0;} else {// process(arr, index + 1, rest) 不要当前的钱// process(arr, index + 1, rest - arr[index]) 要当前的钱,则剩余钱数rest要 减去 arr[index]return process(arr, index + 1, rest) + process(arr, index + 1, rest - arr[index]);}}

动态规划
根据上面暴力递归代码改写动态规划,可变参数是index(数组下标)和 rest(剩余钱数),并且index可以到达arr.length的位置,rest也可能会为0,所以可以确定 dp[][] 大小为 dp[arr.length + 1][aim + 1]。
根据base case 可以确定 dp[arr.length][0]位置的值是1,其余的按照顺序遍历填充即可。

代码

    public static int dp(int[] arr, int aim) {if (aim == 0) {return 1;}int N = arr.length;int[][] dp = new int[N + 1][aim + 1];dp[N][0] = 1;for (int index = N - 1; index >= 0; index--) {for (int rest = 0; rest <= aim; rest++) {dp[index][rest] = dp[index + 1][rest] + (rest - arr[index] >= 0 ? dp[index + 1][rest - arr[index] ]: 0);}}return dp[0][aim];}

题目2
arr是面值数组,其中的值都是正数且没有重复。再给定一个正数aim。每个值都认为是一种面值,且认为张数是无限的。返回组成aim的方法数
例如:arr = {1,2},aim = 4
方法如下:1+1+1+1、1+1+2、2+2
一共就3种方法,所以返回3

暴力递归
整体思路依然是先从暴力递归代码开始写起,并根据暴力递归代码改写动态规划。
暴力递归方法的整体思路是这样:
依然是数组从左向右的不停尝试,因为数组中每个数值张数都可以当做是无限的,所以利用循环来看当前数值的数使用0张情况,使用1张情况,使用2张情况。。。 将结果值进行累加,即为总方法数。
所以暴力递归方法需要的参数:1. arr数组 2.rest 剩余钱数 3. index 数组下标
确定了暴力递归的尝试方法后,base case也就自然而然的出来了,那就是当 index = arr.length时,数组中没有钱可以取了,此时如果剩余钱数 rest = 0,说明利用数组中数值正好拼凑出来了正好的钱数,return 1。 否则 return 0。

代码

public static int coinsWay(int[] arr, int aim) {if (arr == null || arr.length == 0) {return -1;}return process(arr, aim, 0);}public static int process(int[] arr, int rest, int index) {if (index == arr.length) {return rest == 0 ? 1 : 0;}int ways = 0;//循环:当前数值从0张开始,不停尝试, 条件是 当前面值的钱使用几张,都不可以超过剩余钱数 rest。for (int zhang = 0; zhang * arr[index] <= rest; zhang++) {ways += process(arr, rest - zhang * arr[index], index + 1);}return ways;}

动态规划
根据暴力递归方法代码可以确定出可变参数为剩余钱数 rest 和 数组下标 index。
又因为代码中 index是可以到达数组arr的长度的,并且剩余钱数rest可以为 0 。所以dp[][] 的范围是 dp[arr.length + 1] [ rest + 1]。

第二步要根据base case来给dp表进行初始化赋值。代码中 当index = arr.length时,如果rest = 0 则return 1。其余情况 return 0。
int[] 创建后默认值就是 0 ,所以 dp[ arr.length ][ 0 ]位置的值 = 1。 其余照搬暴力递归代码即可。

代码

 public static int dp(int[] arr, int aim) {if (arr == null || arr.length == 0) {return -1;}int N = arr.length;int[][] dp = new int[N + 1][aim + 1];dp[N][0] = 1;for (int index = N - 1; index >= 0; index--) {for (int rest = 0; rest <= aim; rest++) {int ways = 0;for (int zhang = 0; zhang * arr[index] <= rest; zhang++) {ways += dp[index + 1][rest - zhang * arr[index]];}dp[index][rest] = ways;}}return dp[0][aim];}

优化

关于上面的代码。逻辑上已经跑通了,但是关于dp表的整体构建生成还是过于复杂,因为zhang的for中枚举了数组中的每个数值的从 0 ~ zhang * arr[index] <= rest的所有情况。
如果有从暴力递归转动态规划(一)看到现在的会有发现,早期文章中整体的解题过程是 暴力递归 -》 傻缓存(记忆化搜索 - 看dp表中是否有当前值,没有则进行计算) -》 严格表结构依赖(根据每个格子的依赖关系,从底层向上构建dp表)
没有枚举过程时,傻缓存和严格表结构依赖的时间复杂度相同
但是这道题不太一样,因为之前的题目中,构建dp表中每个位置都是一个常数时间操作,而这道题里。构建dp表的每个格子都要进行for循环,这种情况下,如果不进行优化,那么时间复杂度大大增加(时间复杂度看枚举过程中的分支数量)。

思路
这种优化最简单直观的方法就是进行画图。将暴力递归方法中代码,根据依赖关系,直观的展现在图示中。
在这里插入图片描述
拿这张图举例,当rest = 11时(√位置),来看下它的依赖关系,根据暴力递归代码 index + 1 ,rest - zhang * arr[index],最开始使用的是0张,所以是rest - 0,那么在表中的依赖关系就是a位置,接下来是来使用1张、使用2张、使用3张。分别依赖的是bcd的位置。
那当rest = 8 时呢?此时使用1张2张3张,依赖的就是bcd位置。那 √ 位置的结果,之前是a+b+c+d,找到依赖关系后就可以发现,此时用 x + a = √。依赖关系已找到,可以根据这种严格的表结构依赖来进行优化。

代码

public static int bestDP(int[] arr, int aim) {if (arr == null || arr.length == 0) {return -1;}int N = arr.length;int[][] dp = new int[N + 1][aim + 1];dp[N][0] = 1;for (int index = N - 1; index >= 0; index--) {for (int rest = 0; rest <= aim; rest++) {dp[index][rest] = dp[index + 1][rest];//如果左边不越界if (rest - arr[index] >= 0) {dp[index][rest] += dp[index][rest - arr[index]];}}}return dp[0][aim];}

题目3
arr是货币数组,其中的值都是正数。再给定一个正数aim。
每个值都认为是一张货币,认为值相同的货币没有任何不同,返回组成aim的方法数
例如:arr = {1,2,1,1,2,1,2},aim = 4 方法:1+1+1+1、1+1+2、2+2
一共就3种方法,所以返回3。

暴力递归
这个题目和第二题类似,区别在于第二题中每种面值是无限张的,不过这里是有限张数。所以这道题的整体解题思路也和第二题类似。

  1. 先将给定数组进行封装,封装的类中有 int[] coins类型 代表每种不同的面值,以及 int[] zhangs 代表着每种面值对应的张数。
  2. 暴力递归过程也和第二题类似,不过需要判断,for循环中每种面值使用的张数不能大于 zhangs[index] 中的值,以及 zhang * coins[index] 也要不能大于rest。

代码

//将给定的arr封装成Info对象
static class Info {int[] coins;int[] zhangs;public Info(int[] coins, int[] zhangs) {this.coins = coins;this.zhangs = zhangs;}public int[] getCoins() {return coins;}public int[] getZhangs() {return zhangs;}}// arr转换成Info的方法类public static Info getInfo(int[] arr) {Map<Integer, Integer> infoMap = new HashMap<>();for (int num : arr) {if (!infoMap.containsKey(num)) {infoMap.put(num, 1);} else {infoMap.put(num, (infoMap.get(num) + 1));}}int N = infoMap.size();int[] coins = new int[N];int[] zhangs = new int[N];int index = 0;for (Map.Entry<Integer, Integer> entries : infoMap.entrySet()) {coins[index] = entries.getKey();zhangs[index++] = entries.getValue();}return new Info(coins, zhangs);}//暴力递归主方法public static int coinsWay(int[] arr, int aim) {if (arr == null || arr.length == 0) {return -1;}Info info = getInfo(arr);return process(info.getCoins(), info.getZhangs(), aim, 0);}public static int process(int[] coins, int[] zhangs, int rest, int index) {if (index == zhangs.length) {return rest == 0 ? 1 : 0;}int ways = 0;// 判断的逻辑: //zhang 要 <= 每种面值给定的张数//rest - 面值 * 使用的张数 >= 0			for (int zhang = 0; zhang <= zhangs[index] && zhang * coins[index] <= rest; zhang++) {ways += process(coins, zhangs, rest - zhang * coins[index], index + 1);}return ways;}

动态规划
动态规划思路也和第二题大致相同,根据可变参数index和rest确定dp表范围,而后循环遍历填充dp表。

代码

 public static int dp(int[] arr, int aim) {if (arr == null || arr.length == 0) {return -1;}Info info = getInfo(arr);int[] zhangs = info.getZhangs();int[] coins = info.getCoins();int N = zhangs.length;int[][] dp = new int[N + 1][aim + 1];dp[N][0] = 1;for (int index = N - 1; index >= 0; index--) {for (int rest = 0; rest <= aim; rest++) {int ways = 0;for (int zhang = 0; zhang <= zhangs[index] && zhang * coins[index] <= rest; zhang++) {ways += dp[index + 1][rest - zhang * coins[index]];}dp[index][rest] = ways;}}return dp[0][aim];}

优化
动态规划中又见到了填充dp表时的枚举过程,所以可以根据严格的表结构依赖,根据画图,找到每个格子在dp表中的依赖关系,从而替代代码中的枚举for循环。
需要注意的是:这次的优化和第二题不同的在于,每种张数是固定的,而第二题每种张数是无限张的
所以,要考虑到张数的限制,还是回归到图示当中去。
数组中面值有 1,3,5 每种面值各2张,依然是rest = 11,面值 = 3时为例。
在这里插入图片描述

当rest = 11时,面值为3的情况一共有abc三种,对应着3面值的数使用了0、1、2张的情况。
再来看X的位置,此时情况为bcd,第二题要想求√位置,需要 x + a,因为是无限张,但是本题中。如果依然是X + a,那么会多加一个d,张数的使用会多一张。所以在求√时,需要将d位置减掉。
接下来我们将它抽象化一下,根据暴力递归的代码带入公式。
假设此时是m面值,共有n张 , √此时在 i 行,剩余钱数rest,。
那此时a位置就是:dp[index + 1][rest]
此时的x位置就是: dp[index][rest - 一张面值]
d的位置就是: dp[index + 1][ rest - 一张面值 * (面值张数 + 1)]

代码

public static int beatDP(int[] arr, int aim) {Info info = getInfo(arr);int[] zhangs = info.getZhangs();int[] coins = info.getCoins();int N = zhangs.length;int[][] dp = new int[N + 1][aim + 1];dp[N][0] = 1;for (int index = N - 1; index >= 0; index--) {for (int rest = 0; rest <= aim; rest++) {//先取得下面的dp[index][rest] = dp[index + 1][rest];//如果左侧也有,那么就 累加if (rest - coins[index] >= 0) {dp[index][rest] += dp[index][rest - coins[index]];}//如果左侧不越界,则减去多余的那一个if (rest - coins[index] * (zhangs[index] + 1) >= 0) {dp[index][rest] -= dp[index + 1][rest - coins[index] * (zhangs[index] + 1)];}}}return dp[0][aim];}

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

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

相关文章

【不用开发板学习STM32】可设置电子时钟

• 实验环境 工程文件下载链接&#xff01;https://mp.weixin.qq.com/s?__bizMzU2OTc4ODA4OA&mid2247551559&idx1&sn721b9238bc58936ac41e6ad1b9988554&chksmfcfb1990cb8c9086490b11c05bc76c08da15c71caa38715a047c49d36f25a149920aee482f3e&token204641…

SAP SPAD新建打印纸张

SAP SPAD新建打印纸张 1.事务代码SPAD 2.完全管理&#xff0d;设备类型&#xff0d;页格式-显示(创建格式页) 3.按标准A4纸张为模板参考创建。同一个纸张纵向/横向各创建1次(创建格式页) 4.完全管理&#xff0d;设备类型&#xff0d;格式类型-显示(创建格式类型&#xff0…

10、SpringCloud -- 优化重复下单

目录 优化重复下单问题的产生&#xff1a;需求&#xff1a;思路&#xff1a;代码&#xff1a;测试&#xff1a; 优化重复下单 之前超卖、重复下单的解决方式 问题的产生&#xff1a; 比如这个秒杀活动没人去玩&#xff0c;只有一个人去参与秒杀&#xff0c;然后秒杀成功了&a…

vue+Fullcalendar

vueFullcalendar: vueFullcalendar项目代码https://gitee.com/Oyxgen404/vue--fullcalendar.git

【错误解决方案】ModuleNotFoundError: No module named ‘transformers‘

1. 错误提示 在python程序中&#xff0c;尝试导入一个名为transformers的模块&#xff0c;但Python提示找不到这个模块。 错误提示&#xff1a;ModuleNotFoundError: No module named ‘transformers‘ 2. 解决方案 所遇到的问题是Python无法找到名为transformers的模块&am…

Angular-04:指令

① 内置指令1.1 *ngIf 结构指令1.2 [hidden] 属性指令1.3. *ngFor 结构指令1.4 *ngSwitch 结构指令 ② 自定义指令用法 指令是angular操作dom的途径&#xff0c;分为属性指令和结构指令。属性指令&#xff1a;修改元素的外观或行为。使用 [ ] 包裹。结构指令&#xff1a;增加、…

goctl 安装步骤

goctl&#xff1a;go-zero框架强大的项目脚手架工具&#xff0c;一个简单易用的代码生成工具。 go-zero官网&#xff1a;https://go-zero.dev/ go-zero 官网上面对 goctl 的介绍&#xff1a;goctl读作go control&#xff0c;不要读成go C-T-L。goctl的意思是不要被代码控制&a…

jenkins、ant、selenium、testng搭建自动化测试框架

如果在你的理解中自动化测试就是在eclipse里面讲webdriver的包引入&#xff0c;然后写一些测试脚本&#xff0c;这就是你所说的自动化测试&#xff0c;其实这个还不能算是真正的自动化测试&#xff0c;你见过每次需要运行的时候还需要打开eclipse然后去选择运行文件吗&#xff…

力扣:144. 二叉树的前序遍历(Python3)

题目&#xff1a; 给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 示例&#xff1a; 示例 1&#xff1a; 输…

JavaEE-博客系统1(数据库和后端的交互)

本部分内容包括网站设计总述&#xff0c;数据库和后端的交互&#xff1b; 数据库操作代码如下&#xff1a; -- 编写SQL完成建库建表操作 create database if not exists java_blog_system charset utf8; use java_blog_system; -- 建立两张表&#xff0c;一个存储博客信息&am…

【C++基础入门】44.C++中对象模型分析(上)

一、回归本质 class 是一种特殊的 struct 在内存中 class 依旧可以看作变量的集合class 与 struct 遵循相同的内存对齐规则class 中的成员函数与成员变量是分开存放的 每个对象有独立的成员变量所有对象共享类中的成员函数值得思考的问题 下面看一个对象内存布局的代码&#x…

汽车行驶性能的主观评价方法(2)-驾驶员的任务

人&#xff08;驾驶员&#xff09;-车辆-环境闭环控制系统 驾驶过程中&#xff0c;驾驶员承担着操纵车辆和控制车辆的任务。驾驶员在不知不觉中接受了大量光学、声学和动力学信息并予以评价&#xff0c;同时不断地通过理论值和实际值的比较来完成控制作用&#xff08;图 2.1&a…

杀毒软件哪个好,杀毒软件有哪些

安全杀毒软件是一种专门用于检测、防止和清除计算机病毒、恶意软件和其他安全威胁的软件。这类软件通常具备以下功能&#xff1a; 1. 实时监测&#xff1a;通过实时监测计算机系统&#xff0c;能够发现并防止病毒、恶意软件等安全威胁的入侵。 2. 扫描和清除&#xff1a;可以…

【ICCV2023】频率成分在少样本学习中的重要性

论文标题&#xff1a;Frequency Guidance Matters in Few-Shot Learning 论文链接&#xff1a;https://openaccess.thecvf.com/content/ICCV2023/html/Cheng_Frequency_Guidance_Matters_in_Few-Shot_Learning_ICCV_2023_paper.html 代码&#xff1a;暂未开源 引用&#xff1a;…

基于单片机设计的防煤气泄漏装置

一、前言 煤气泄漏是一个严重的安全隐患&#xff0c;可能导致火灾、爆炸以及对人体健康的威胁。为了提高家庭和工业环境中煤气泄漏的检测和预防能力&#xff0c;设计了一种基于单片机的防煤气泄漏装置。 单片机选择STC89C52作为主控芯片。为了检测煤气泄漏&#xff0c;采用了…

【JavaSE】运算符详解及与C语言中的区别

在文章的最后&#xff0c;总结了Java与C语言的某些不同点 目录 一、什么是运算符 二、算术运算符 1.基本四则运算符 2.增量运算符 3.自增/自减运算符/-- 三、关系运算符 四、逻辑运算符&#xff08;重点&#xff09; 1.逻辑与&& 2.逻辑或|| 3.逻辑非 4.补…

elasticsearch一些重要的配置参数

先看一下官网给我们提供的全部的参数配置项 官网地址 官方文档链接&#xff1a;注意版本是8.1Configuring Elasticsearch | Elasticsearch Guide [8.1] | Elastic​编辑https://www.elastic.co/guide/en/elasticsearch/reference/current/settings.html 重要&#xff08;基本…

零基础Linux_24(多线程)线程同步+条件变量+生产者消费模型_阻塞队列版

目录 1. 线程同步和生产者消费者模型 1.1 生产者消费者模型的概念 1.2 线程同步的概念 1.3 生产者消费者模型的优点 2. 线程同步的应用 2.1 条件变量的概念 2.2 条件变量操作接口 3. 生产者消费者模型_阻塞队列 3.1 前期代码&#xff08;轮廓&#xff09; 3.2 中期代…

phar反序列化学习

PHP反序列化常见的是使用unserilize()进行反序列化&#xff0c;除此之外还有其它的反序列化方法&#xff0c;不需要用到unserilize()。就是用到phar反序列化。 Phar phar文件 Phar是将php文件打包而成的一种压缩文档&#xff0c;类似于Java中的jar包。它有一个特性就是phar文…

Golang教程——配置环境,再探GoLand

文章目录 一、Go是什么&#xff1f;二、环境配置验证配置环境变量 三、安装开发者工具GoLand四、HelloGolang 一、Go是什么&#xff1f; Go&#xff08;也称为Golang&#xff09;是一种开源的编程语言&#xff0c;由Google开发并于2009年首次发布。Go语言旨在提供一种简单、高…