【算法 - 动态规划】找零钱问题Ⅲ

在前面的动态规划系列文章中,关于如何对递归进行分析的四种基本模型都介绍完了,再来回顾一下:

  1. 从左到右模型arr[index ...]index 之前的不用考虑,只考虑后面的该如何选择
  2. 范围尝试模型 :思考 [L ,R] 两端,即 开头和结尾 处分别该如何取舍。
  3. 样本对应模型 :以 结尾位置 为出发点,思考两个样本的结尾都会产生哪些可能性 。
  4. 业务限制模型 :不能够明确的知道一个参数的变化范围,通过业务的限制找到 最差情况 进行估计。

前两篇文章我们讲解了两道相似的找零钱问题。今天我们继续“找零钱”。

找零钱问题 Ⅲ

给定一个 面值 数组 arr ,其中的值均为无重复的正数,每一个值代表一种面值,张数无限。求能够组成 aim 最少的货币数量。

示例 1:

输入: arr = {1, 2} ,aim = 4 。

输出: 2

解释: 共四种组合方式,其中最少需要两张就能组成 4。

  • 1 + 1 + 1 + 1 = 4
  • 1 + 1 + 2 = 4
  • 2 + 2 = 4

示例 2:

输入: arr = {1, 2, 5} ,aim = 6 。

输出: 2

解释: 共五种组合方式,其中最少需要两张就能组成 6。

  • 1 + 1 + 1 + 1 + 1 + 1 = 6
  • 1 + 1 + 1 + 1 + 2 = 6
  • 1 + 1 + 2 + 2 = 6
  • 2 + 2 + 2 = 6
  • 1 + 5 = 6

注意: 要区分好与 前两篇零钱问题 的区别哦!

  • 三篇文章共性:相同面值货币无区别
  • 前篇文章零钱问题Ⅰ:张数不限求总计
  • 上篇文章零钱问题Ⅱ:张数有限求总计
  • 本篇文章零钱问题Ⅲ:张数不限求最少

首先我们依然采用最朴素的 暴力递归 来思考这道题目。

思路

这三道题目都是典型的 从左到右模型 ,因此,递归就可以按照 在 arr[index ...] 数组中,index 之前的不用考虑,只考虑后面的该如何选择 的思路来划分情况:

  • 当前 index 下标对应的面值 参与 组合,选择不同的张数 ,之后能有多少种情况。

因为要求张数最小的情况,因此要返回所有情况的 最小值

代码

public static int minCoins(int[] arr, int aim) {return process(arr, 0, aim);
}public static int process(int[] arr, int index, int rest) {if (index == arr.length) {return rest == 0 ? 0 : Integer.MAX_VALUE;} else {int ans = Integer.MAX_VALUE;for (int zhang = 0; zhang * arr[index] <= rest; zhang++) {int next = process(arr, index + 1, rest - zhang * arr[index]);if (next != Integer.MAX_VALUE) {ans = Math.min(ans, zhang + next);}}return ans;}
}

代码解释

递归中,base case 为下标来到最后时后边没有货币了,如果此时剩余的钱数也为 0 ,说明不需要任何一张钱了,即返回 0。

选择多少张数 体现在 zhang 从 0 开始,直到该张数的面值超过了剩余钱数 rest 为止。

继续调用递归且下标 index + 1 ,剩余钱数也相应减少。如果之后的返回值不为 系统最大值 ,说明之后的情况中有可以选择的方式,那就和 ans 一起取两者中更小的即为答案。


写出该暴力版的递归之后修改出动态规划版的就很容易了。

动态规划版

public static int dp(int[] arr, int aim) {if (aim == 0) {return 0;}int N = arr.length;int[][] dp = new int[N + 1][aim + 1];dp[N][0] = 0;for (int j = 1; j <= aim; j++) {dp[N][j] = Integer.MAX_VALUE;}for (int index = N - 1; index >= 0; index--) {for (int rest = 0; rest <= aim; rest++) {int ans = Integer.MAX_VALUE;for (int zhang = 0; zhang * arr[index] <= rest; zhang++) {int next = dp[index + 1][rest - zhang * arr[index]];if (next != Integer.MAX_VALUE) {ans = Math.min(ans, zhang + next);}}dp[index][rest] = ans;}}return dp[0][aim];
}

代码解释

可变的参数有两个:总的面值个数 N 和 剩余的目标钱数 rest 。因此,需要设置一个二维的 dp 表数组,由于 N, rest 的取值范围是 0~N 、0~aim ,因此数组大小设置为 dp[N + 1][aim + 1]

递归代码 index == arr.length 可知,初始只有 dp[N][0] 的值为 0 ,其余的值均设置为系统最大值。

因为递归中只依赖 index + 1 的值,所以 dp 表倒着填写。

写法和递归中的一样,只需将递归调用换成从 dp 数组中取值就行。

根据递归调用 process(arr, 0, aim) 可知最终返回 dp[0][aim]


观察递归的代码,发现竟然有 3 层 for 循环。为什么呢?

思考后发现, dp 表中的每个位置同样需要 枚举 后才能知道(从 0 张开始一直枚举到超过剩余值 rest)。那有没有办法消掉这层枚举的 for 循环呢?答案是有的!

下面我们通过画 dp 表,探寻该动态规划应如何进一步优化。

假设此时剩余的总钱数 rest = 10,面值数 arr[i] = 3 。

一图胜千言~

通过枚举代码可知,arr[i][10] 的值,红色 = min(黄色+0, 紫色+1, 紫色+2, 紫色+3,)

黄色:不选面值为 3 的钱币时,rest 仍为 10,依赖下一格 i + 1。

紫色:分别选 1 张、2 张、3张…时,rest 对应每次减 3 ,且依赖下一格 i + 1 行。那么所用的总张数就需要分别加上1,2,3再来比较最小值。

稍加思考发现,蓝色的位置即 arr[i][10 - 3] 位置的值正是 3 个紫色加完0,1,2之后的最小值。

那么,就可以改为 红色 = (黄色, 蓝色+1),这样就不需要一直往前寻找了,减少一个 for 循环

情况 2:

如果蓝色 位置本身不存在(越界了,小于 0 了)或者蓝色的 值不存在(没有有效方案,值为系统最大值)。

这里只需稍加判断一下即可!

最终优化版动态规划

public static int dp(int[] arr, int aim) {if (aim == 0) {return 0;}int N = arr.length;int[][] dp = new int[N + 1][aim + 1];dp[N][0] = 0;// 除了 dp[N][0] 外都设置为 系统最大值for (int j = 1; j <= aim; j++) {dp[N][j] = Integer.MAX_VALUE;}for (int index = N - 1; index >= 0; index--) {for (int rest = 0; rest <= aim; rest++) {// 先把 红色 设置为 黄色dp[index][rest] = dp[index + 1][rest];// 如果有蓝色位置 且蓝色位置有效(不是系统最大)// 那就再比较黄色 和 蓝色 + 1 的大小 ,取小者if (rest - arr[index] >= 0 && dp[index][rest - arr[index]] != Integer.MAX_VALUE) {dp[index][rest] = Math.min(dp[index][rest], dp[index][rest - arr[index]] + 1);}}}return dp[0][aim];
}

注意看越界的判断哦,黄色、蓝色分开计算。这样就完成了最终版的动态规划~

通过本文的学习相信小伙伴对为什么有了记忆化搜索还要写出 严格的表依赖 有了更加深刻的理解!!

为了避免枚举行为多产生的 for 循环,有了 表依赖 才能找到如何 优化枚举 !这种方法也叫做 斜率优化
因此,前面学习的如何一步步的将暴力递归修改为严格表依赖动态规划的基础要打牢哦!还不会的赶快关注一下回顾前面的几篇文章吧!

~ 点赞 ~ 关注 ~ 评论 ~ 不迷路 ~

------------- 往期回顾 -------------
【算法 - 动态规划】找零钱问题Ⅰ
【算法 - 动态规划】找零钱问题Ⅱ
【算法 - 动态规划】原来写出动态规划如此简单!
【算法 - 动态规划】最长公共子序列问题
【算法 - 动态规划】最长回文子序列
【算法 - 动态规划】力扣 691. 贴纸拼词
【算法 - 动态规划】京东面试题 - 洗咖啡杯问题
【堆 - 专题】“加强堆” 解决 TopK 问题!
AC 此题,链表无敌!!!

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

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

相关文章

美国硅谷云服务器租用注意事项

随着信息技术的飞速发展&#xff0c;云服务器已成为众多企业和个人用户首选的IT基础设施。美国硅谷作为全球科技创新的摇篮&#xff0c;其云服务器市场也备受关注。然而&#xff0c;在租用硅谷云服务器时&#xff0c;用户需要注意以下几个方面&#xff0c;以确保获得优质、稳定…

最小的图片有什么用

你知道最小的图片大小是多大么&#xff1f;比如最小的 GIF 格式图片&#xff0c;仅有 35 bytes&#xff0c;面积是 1 1 px。 这么小的图片可能只能表达一个像素&#xff0c;这有什么意义呢&#xff1f; 在互联网早起&#xff0c;也就是静态网页居多&#xff0c;还在使用html…

RabbitMQ开启TLS支持,解决AMQP明文身份验证漏洞

随着网络通信安全性的日益重要&#xff0c;我们不难发现&#xff0c;在企业级应用中&#xff0c;数据传输的安全防护措施已经成为不可或缺的一环。近期&#xff0c;不少使用RabbitMQ的开发团队收到了关于“远程主机允许明文身份验证”的漏洞警告。为确保敏感信息的安全传递&…

4核8G服务器选阿里云还是腾讯云?价格性能对比

4核8G云服务器多少钱一年&#xff1f;阿里云ECS服务器u1价格955.58元一年&#xff0c;腾讯云轻量4核8G12M带宽价格是646元15个月&#xff0c;阿腾云atengyun.com整理4核8G云服务器价格表&#xff0c;包括一年费用和1个月收费明细&#xff1a; 云服务器4核8G配置收费价格 阿里…

新加坡服务器托管:开启全球化发展之门

新加坡作为一个小国家&#xff0c;却在全球范围内享有极高的声誉。新加坡作为亚洲的科技中心&#xff0c;拥有先进的通信基础设施和成熟的机房托管市场。除了其独特的地理位置和发达的经济体系外&#xff0c;新加坡还以其开放的商业环境和便利的托管服务吸引着越来越多的国际公…

C#之WPF学习之路(6)

目录 内容控件&#xff08;3&#xff09; GroupBox标题容器控件 ScrollViewer控件 ScrollBar滚动条 Slider滑动条 ProgressBar进度条 Calendar日历控件 DatePicker日期控件 Expander折叠控件 MediaElement媒体播放器 内容控件&#xff08;3&#xff09; GroupBox标题…

在两台CentOS 7服务器上部署MinIO集群---准确

环境说明&#xff1a; 2台Centos7服务器 IP地址分别为172.16.1.9和172.16.1.10 1. 创建minio用户和目录 在两台服务器上执行以下命令&#xff1a; sudo useradd -m -d /app/minio minio sudo mkdir -p /app/minioData sudo mkdir -p /app/minio/logs sudo chown -R mini…

2024年2月26日-3月3日(全面进行+收集免费虚幻商城资源,)

依旧按照月计划进行。 周一&#xff1a; 18&#xff1a;40–19:50,测量学基础&#xff08;4.1&#xff09;&#xff0c;数字图像处理3.4&#xff0c; 周二&#xff1a; 6&#xff1b;00-8&#xff1a;50测量学基础&#xff08;4.1&#xff09;&#xff0c;数字图像处理3.5&…

解析数据挖掘:发掘数据中的宝藏

数据挖掘作为一种从大规模数据中发现模式、规律和知识的技术和方法&#xff0c;已经在各个领域展现了强大的应用价值。本文将从数据挖掘的定义、流程、常用算法以及应用场景等方面进行科普介绍&#xff0c;帮助读者深入了解数据挖掘的概念及其在实际应用中的重要作用。 1. 数据…

vue项目打包获取git commit信息并输出到打包后的指定文件夹中

需求背景&#xff1a; 前端项目经常打包&#xff0c;发包部署&#xff0c;为了方便测试及运维发现问题时与正确commit信息对比 实现方式&#xff1a; 使用Node.js的child_process模块来执行git命令 实现步骤&#xff1a; 1.在package.json的同级目录下新建一个version.js文件。…

459. 重复的子字符串(力扣LeetCode)

文章目录 459. 重复的子字符串题目描述暴力移动匹配KMP算法 459. 重复的子字符串 题目描述 给定一个非空的字符串 s &#xff0c;检查是否可以通过由它的一个子串重复多次构成。 示例 1: 输入: s “abab” 输出: true 解释: 可由子串 “ab” 重复两次构成。 示例 2: 输入: …

【C++进阶】哈希(万字详解)—— 学习篇(上)

&#x1f387;C学习历程&#xff1a;入门 博客主页&#xff1a;一起去看日落吗持续分享博主的C学习历程博主的能力有限&#xff0c;出现错误希望大家不吝赐教分享给大家一句我很喜欢的话&#xff1a; 也许你现在做的事情&#xff0c;暂时看不到成果&#xff0c;但不要忘记&…

Java 枚举(Enum)简介

Java 枚举&#xff08;Enum&#xff09;是一种特殊的类&#xff0c;它用于定义一组固定的常量值。枚举提供了一种类型安全的方式来表示一组固定的值。在 Java 中&#xff0c;枚举是实现单例模式的一种方式&#xff0c;并且它们是 Java 语言的一部分&#xff0c;而不是类库的一部…

30天自制操作系统(第21天)

21.1 攻克难题——字符串显示API 显示单个字符时&#xff0c;用 [CS:ECX] 的方式特意指定了 CS&#xff08;代码段寄存器&#xff09;&#xff0c;因此可以成功读取 msg的内容。但在显示字符串时&#xff0c;由于无法指定段地址&#xff0c;程序误以为是 DS而从完全错误的内存地…

ChatGPT:你真的了解网络安全吗?浅谈攻击防御进行时之传统的网络安全

ChatGPT&#xff08;全名&#xff1a;Chat Generative Pre-trained Transformer&#xff09;&#xff0c;美国OpenAI 研发的聊天机器人程序&#xff0c;是人工智能技术驱动的自然语言处理工具。 基于其语言模型庞大、可控制、具有高度扩展性的特点&#xff0c;本文通过对话Chat…

111期_C++_2024年1月份作业博客_选择题错题总结

一、野指针问题&#xff1a;在定义的时候没有初始化&#xff0c;就不能能用scanf 或 printf 二、一个变量出现在表达式的两边作为两个不同的操作数&#xff0c; 并且其中一个操作数带有&#xff0c;此时表达式出现歧义 三、两端出栈问题&#xff1a; 错因&#xff1a;未理解题…

素皮材质的手机壳,如何才能做到经久耐用?

近几年&#xff0c;素皮材质开始在手机背壳上开始应用&#xff0c;各家手机厂商&#xff0c;基本都给自己的旗舰系列设备推出了带素皮材质版本的手机款式&#xff0c;比如华为的Mate 60系列&#xff0c;不仅Pro版本有素皮材质&#xff0c;Pro版本更是黑白两款全是素皮材质。 那…

Docker的基础知识与应用技巧

文章目录 一.docekr简介二.docekr安装三.docker命令 一.docekr简介 Docker是一个开源的应用容器引擎&#xff0c;它可以让开发者打包他们的应用以及依赖包到一个可移植的镜像中&#xff0c;然后发布到任何流行的Linux或Windows操作系统的机器上。Docker基于轻量级虚拟化技术&a…

交易这条路,没有永远的大神,唯有攀登者

一、摆平心态 很多人一谈及金融&#xff0c;首先想到的就是赚钱&#xff0c;而忽视了亏损和风险的存在。面对亏损时&#xff0c;许多人不是选择止损&#xff0c;而是选择加仓&#xff0c;希望通过大赚一笔来弥补损失。然而&#xff0c;这种做法往往会让他们在市场中受到惨痛的教…

桥接模式(Bridge Pattern) C++

上一节&#xff1a;适配器模式&#xff08;Adapter Pattern&#xff09; C 文章目录 0.理论1.组件2.使用场景 1.实践 0.理论 桥接模式&#xff08;Bridge Pattern&#xff09;是一种结构型设计模式&#xff0c;它的核心思想是将抽象部分与其实现部分分离&#xff0c;使它们可…