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

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

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

接下来的几篇文章我们继续深挖动态规划的一些 优化策略

通过前面文章的学习,相信小伙伴都能够根据不同模型的套路熟练的改出 严格表依赖 的动态规划版本了。

但有个问题?

记忆化搜索严格 dp 表依赖 的时间复杂度一样,记忆化搜索的代码写起来容易,为什么还非要找到动态规划的 状态转移方程 ,进而修改成为严格表依赖关系的呢。接下来我们通过一道题目 揭晓答案

找零钱问题Ⅰ

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

示例 1:

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

输出: 3

解释: 共三种组合方式

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

示例 2:

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

输出: 5

解释: 共五种组合方式

  • 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 下标对应的面值 参与 组合,选择任意张数,之后能有多少种情况;
  • 当前 index 下标对应的面值 不参与 组合,选择任意张数,之后能有多少种情况。

因为要求总的情况,因此要返回这两种情况的 总和

代码

public static int coinsWay(int[] arr, int aim) {if (arr == null || arr.length == 0 || aim < 0) {return 0;}return process(arr, 0, aim);
}public static int process(int[] arr, int index, int rest) {if (index == arr.length) { return rest == 0 ? 1 : 0;}int ways = 0;for (int zhang = 0; zhang * arr[index] <= rest; zhang++) {ways += process(arr, index + 1, rest - (zhang * arr[index]));}return ways;
}

代码解释

递归中,base case 为下标来到最后时,如果剩余的钱数为 0 ,说明前面的组合刚好能够凑出 aim 值,记为有效的一种情况。

选和不选 体现在 zhang 从 0 开始,直到该张数的面值超过了剩余钱数 rest 为止。继续调用递归且下标 index + 1 ,剩余钱数也相应减少。最终返回总的方法数即可。


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

动态规划版

public static int dp(int[] arr, int aim) {if (arr == null || arr.length == 0 || aim < 0) {return 0;}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];
}

代码解释

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

递归代码 index == arr.length 可知,初始只有 dp[N][0] 的值为 1 。

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

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


之前的文章写到这里就结束了,但 这次不一样

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

思考后发现, dp 表中的每个位置同样需要 枚举 后才能知道(之前的题目能够直接计算出来)。那有没有办法消掉这层枚举的 for 循环呢?答案是有的!

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

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

一图胜千言~

通过枚举代码可知,arr[i][10] 的值,红色 = 黄色 + 3 个紫色。

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

紫色:分别选 1 张、2 张、3张…时,rest 对应每次减 3 ,且依赖下一格 i + 1 行。

稍加思考发现,蓝色的位置即 arr[i][10 - 3] 位置的值正是 3 个紫色的总和。那么,就可以将 红色 = 黄色 + 3 个紫色,改为 红色 = 黄色 + 蓝色,这样就不需要一直往前寻找了,减少一个 for 循环

最终优化版动态规划

public static int dp(int[] arr, int aim) {if (arr == null || arr.length == 0 || aim < 0) {return 0;}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];
}

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

回答最初的问题,为什么有了记忆化搜索还要写出严格的表依赖呢?

为了避免枚举行为多产生的 for 循环,有了 表依赖 才能找到如何 优化枚举

因此,前面学习的如何一步步的将暴力递归修改为严格表依赖动态规划的基础要打牢哦!还不会的赶快关注一下回顾前面的几篇文章吧!

~ 点赞 ~ 关注 ~ 不迷路 ~!!!

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

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

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

相关文章

【BUG 记录】MyBatis-Plus 处理枚举字段和 JSON 字段

【BUG 记录】MyBatis-Plus 处理枚举字段和JSON字段 一、枚举字段&#xff08;mysql环境已测、postgresql环境已测&#xff09;1.1 场景1.2 定义枚举常量1.3 配置枚举处理器1.4 测试 二、JSON字段&#xff08;mysql环境已测&#xff09;2.1 导包2.2 使用对象接受2.3 测试 三、JS…

图片录入设备、方式与质量对图片转Excel的影响

随着数字化时代的到来&#xff0c;图片已经成为人们日常生活中不可或缺的一部分。在各行各业中&#xff0c;图片的应用越发广泛&#xff0c;从而促使了图片处理技术的快速发展。然而&#xff0c;图片的质量对于后续数据处理和分析的准确性和可靠性有着至关重要的影响。本文将从…

【k8s配置与存储--持久化存储(PV、PVC、存储类)】

1、PV与PVC 介绍 持久卷&#xff08;PersistentVolume&#xff0c;PV&#xff09; 是集群中的一块存储&#xff0c;可以由管理员事先制备&#xff0c; 或者使用存储类&#xff08;Storage Class&#xff09;来动态制备。 持久卷是集群资源&#xff0c;就像节点也是集群资源一样…

年龄性别预测3:Android实现年龄性别预测和识别(含源码,可实时预测)

年龄性别预测3&#xff1a;Android实现年龄性别预测和识别(含源码&#xff0c;可实时预测) 目录 年龄性别预测3&#xff1a;Android实现年龄性别预测和识别(含源码&#xff0c;可实时预测) 1.年龄性别预测和识别方法 2.人脸检测方法 3.年龄性别预测和识别模型训练 &#…

【Golang】Golang使用embed加载、打包静态资源文件

【Golang】Golang使用embed加载、打包静态资源文件 大家好 我是寸铁&#x1f44a; 总结了一篇Golang使用embed加载静态资源文件的文章✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 前言 事情是这样的&#xff1a;前不久&#xff0c;有同学问我,golang怎么把静态资源文件打包成一…

【Node.js】自动生成 API 文档

目录 1、直接使用swagger-ui-express 2、配合swagger-jsdoc 如何在Node.js项目中使用 Swagger 来自动生成 API接口文档&#xff0c;使用生成方式有很多种。本文基于swagger-jsdocswagger-ui-express快速实现 1、直接使用swagger-ui-express // 方便来浏览和测试api npm i sw…

GDPU Java 天码行空 1

&#x1f496; 配置环境 &#x1f468;‍&#x1f3eb; JDK17 配置教程 &#x1f338; CMD 查看本机 JDK 版本命令&#xff1a; java -version1. 输出 Hello World&#xff01; (1) 新建 Java 文件 文件名&#xff1a;HelloWorld.java 文件内容&#xff1a; public clas…

家政小程序有哪些功能 怎么制作

随着人们生活节奏的加快&#xff0c;家政服务变得越来越受到人们的青睐。为了提升家政服务的便捷性和高效性&#xff0c;家政小程序成为了越来越受欢迎的选择。下面具体介绍家政小程序有哪些功能&#xff0c;如何制作。 1. 展示家政服务 在小程序中&#xff0c;上传所有的家政…

C# OpenCvSharp DNN Yolov8-OBB 旋转目标检测

目录 效果 模型信息 项目 代码 下载 C# OpenCvSharp DNN Yolov8-OBB 旋转目标检测 效果 模型信息 Model Properties ------------------------- date&#xff1a;2024-02-26T08:38:44.171849 description&#xff1a;Ultralytics YOLOv8s-obb model trained on runs/DOT…

一周学会Django5 Python Web开发-Django5二进制文件下载响应

锋哥原创的Python Web开发 Django5视频教程&#xff1a; 2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~共计25条视频&#xff0c;包括&#xff1a;2024版 Django5 Python we…

【Git教程】(三)提交详解 —— add、commit、status、stach命令的说明,提交散列值与历史,多次提交及忽略 ~

Git教程 提交详解 1️⃣ 访问权限与时间戳2️⃣ add命令与 commit 命令3️⃣ 提交散列值4️⃣ 提交历史5️⃣ 一种特别的提交查看方法6️⃣ 同一项目的多部不同历史6.1 部分输出&#xff1a;-n6.2 格式化输出&#xff1a;--format、--oneline6.3 统计修改信息&#xff1a;--st…

C++初阶 | [八] (下) vector 模拟实现

摘要&#xff1a;vector 模拟实现讲解&#xff08;附代码示例&#xff09;&#xff0c;隐藏的浅拷贝&#xff0c;迭代器失效 在进行 vector 的模拟实现之前&#xff0c;我们先粗略浏览一下 stl_vector.h 文件中的源码来确定模拟实现的大体框架。 这里提供一些粗略浏览源码的技巧…

go环境安装-基于vscode的Windows安装

1、vscode安装 官网链接&#xff1a;https://code.visualstudio.com/ 选择相应的版本&#xff0c;这里选择Windows下的 下载得到一个VSCodeUserSetUp-x64的可执行文件&#xff0c;双击执行&#xff0c;选择要安装的路径&#xff0c;下一步。 2、go语言安装 官网链接&#x…

【Unity自制手册】Unity—Camera相机跟随的方法大全

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

微信小程序的医院体检预约管理系统springboot+uniapp+python

本系统设计的目的是建立一个简化信息管理工作、便于操作的体检导引平台。共有以下四个模块&#xff1a; uni-app框架&#xff1a;使用Vue.js开发跨平台应用的前端框架&#xff0c;编写一套代码&#xff0c;可编译到Android、小程序等平台。 语言&#xff1a;pythonjavanode.js…

Sentinel 动态规则扩展

一、规则 Sentinel 的理念是开发者只需要关注资源的定义&#xff0c;当资源定义成功后可以动态增加各种流控降级规则。Sentinel 提供两种方式修改规则&#xff1a; 通过 API 直接修改 (loadRules)通过 DataSource 适配不同数据源修改 手动通过 API 修改比较直观&#xff0c;…

主机字节序与网络字节序

大端序和小端序 大端序&#xff08;Big Endian&#xff09;和小端序&#xff08;Little Endian&#xff09;是两种计算机存储数据的方式。 大端序指的是将数据的高位字节存储在内存的低地址处&#xff0c;而将低位字节存储在内存的高地址处。这类似于我们阅读多位数时从左往右…

迁移学习 领域自适应

迁移学习 什么是迁移学习 迁移学习是机器学习领域用于标记数据难获取这一基础问题的重要手段&#xff0c; 将训练好的内容应用到新的任务上被称为迁移学习。 由于这个过程发生在两个领域间&#xff0c;已有的知识和数据也就是被迁移的对象被称为源域&#xff0c;被赋予经验…

防火墙的内容安全

目录 1. 内容安全 1.1 IAE引擎 DPI---深度包检测技术 DFI---深度流检测技术 结论(优缺点)&#xff1a; 1.2 入侵防御&#xff08;检测&#xff09;(IPS) IPS的优势: 入侵检测的方法: 入侵检测的流程 签名 查看预定义签名的内容 新建自定义签名 入侵防御的检测…

热闹元宵进行中,如何利用VR全景展示民宿品牌形象?

错峰出游闹元宵&#xff0c;元宵节恰逢周末&#xff0c;而且还是春节假期返工之后的首个休息日&#xff0c;不少人都想通过短途度假来缓解“节后综合征”。两位数的特价机票、打折的各种酒店让你实现“旅行自由”&#xff0c;那么如何知道特价酒店服务好不好呢&#xff1f;先别…