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

news/发布时间2024/5/15 11:18:21

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

  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.bcls.cn/EJwD/5500.shtml

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

相关文章

RF 框架实现企业级 UI 自动化测试

RobotFramework 框架可以作为公司要做自动化 但是又不会代码的一种临时和紧急情况的替代方案&#xff0c;上手简单。 前言 现在大家去找工作&#xff0c;反馈回来的基本上自动化测试都是刚需&#xff01;没有自动化测试技能&#xff0c;纯手工测试基本没有什么市场。 但是很多…

Kotlin多线程

目录 线程的使用 线程的创建 例一&#xff1a;创建线程并输出Hello World Thread对象的用法 start() join() interrupt() 线程安全 原子性 可见性 有序性 线程锁 ReentrantLock ReadWriteLock 线程的使用 Java虚拟机中的多线程可以1:1映射至CPU中&#xff0c;即…

云仓酒庄旗下雷盛红酒成为国际爱乐交响乐团音乐会指定合作品牌

近日&#xff0c;一场震撼视听的古典与流行交响音乐会在北京欢乐谷华侨城大剧院落下帷幕。此次音乐会由国际爱乐交响乐团倾情演绎&#xff0c;吸引了众多音乐爱好者和业界人士的关注。值得一提的是&#xff0c;海南云仓酒庄有限公司旗下的“雷盛”红酒作为本次音乐会的指定合作…

Docker基础篇(五) dockerfile之基础内容

Dockerfile基础内容 1、每条保留字指令都必须大写字母且后面要跟随至少一个参数 –保留字&#xff1a;– FROM、 MAINTAINER 作者、维护者、 RUN、 EXPOSE 暴露、曝光 WORKDIR ENV ADD COPY VOLUME CMD ENTRYPOINT 进入点&#xff0c;入口 ONBUILD 2、指令按照从上到下&#x…

第十三天-mysql交互

目录 1.安装MySQL connector 方式1&#xff1a;直接安装 方式2&#xff1a;下载 2.创建链接 3.游标Cursor 4.事务控制 5. 数据库连接池 1. 使用 6.循环执行SQL语句 不了解mysql的可以先了解mysql基础 1.安装MySQL connector 1. MySQL connector 是MySQL官方驱动模块…

一文1800字从0到1使用Python Flask实战构建Web应用

Python Flask是一个轻量级的Web框架&#xff0c;它简单易用、灵活性高&#xff0c;适用于构建各种规模的Web应用。本文将介绍如何使用Python Flask框架来实战构建一个简单的Web应用&#xff0c;并展示其基本功能和特性。 第一部分&#xff1a;搭建开发环境 在开始之前我们需要…

docker容器技术(2)

docker容器数据卷 什么是数据卷&#xff1f; 在Docker中&#xff0c;数据卷&#xff08;Data Volumes&#xff09;是一种特殊的目录&#xff0c;可以在容器和主机之间共享数据。它允许容器内的文件持久存在&#xff0c;并且可以被多个容器共享和访问。 数据卷的主要作用如下&am…

万户OA ezoffice text2Html接口存在任意文件读取漏洞

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

跨境外贸自动评论脚本开发常用代码!

随着跨境电商的兴起&#xff0c;自动化评论成为了提升销售和客户满意度的重要工具&#xff0c;通过编写自动评论脚本&#xff0c;商家可以快速地在各个平台留下正面评价&#xff0c;提高产品的曝光率和信誉度。 本文将介绍跨境外贸自动评论脚本开发的一些常用代码&#xff0c;…

开源图表库Echarts 简介与基本使用

ECharts 是一个使用 JavaScript 实现的开源可视化图表库&#xff0c;由百度团队开发。它提供了丰富的图表类型&#xff0c;如折线图、柱状图、饼图、地图、雷达图等&#xff0c;并且可以轻松地与其他前端框架和库集成。ECharts 的设计目的是为了满足复杂数据的可视化需求&#…

php伪协议 [SWPUCTF 2022 新生赛]ez_ez_php(revenge)

打开题目 题目源代码如下 <?php error_reporting(0); if (isset($_GET[file])) {if ( substr($_GET["file"], 0, 3) "php" ) {echo "Nice!!!";include($_GET["file"]);} else {echo "Hacker!!";} }else {highlight_fi…

如何压缩pdf文件?几种高效压缩方法收好

如何压缩pdf文件&#xff1f;在日常工作和生活中&#xff0c;我们经常会在工作中使用pdf文件。然而&#xff0c;有时候过大的PDF文件会给我们的传输和存储带来不便。那么&#xff0c;如何有效地压缩PDF文件呢&#xff1f;本文将为你详细介绍几种简单实用的方法&#xff0c;让你…

MySQL的SQL语句

1.MySQL连接 连接命令一般是这样写的 mysql -h$ip -P$port -u$user -p比如:mysql -h127.0.0.1 -P3306 -uroot -p -h 指定连接的主机地址&#xff1b;-P 指定连接端口号&#xff1b;-u 指定用户名 -p指定用户名密码 2.SQL分类 DDL(Data Definition Language) 数据定义语言&…

甲氧基 PEG4 二苯并环辛烯,mPEG4 DBCO,可以与多种基团发生反应

甲氧基四聚乙二醇二苯并环辛烯&#xff0c;甲氧基 PEG4 二苯并环辛烯&#xff0c;mPEG4 DBCO&#xff0c;DBCO mPEG4&#xff0c;m-PEG4-DBCO&#xff0c;mPEG4-DBCO&#xff0c;可以与多种基团发生反应 您好&#xff0c;欢迎来到新研之家 文章关键词&#xff1a;甲氧基四聚乙…

抖音视频评论采集软件|抖音数据抓取工具

抖音视频评论采集软件是一款基于C#开发的高效、便捷的工具&#xff0c;旨在为用户提供全面的数据采集和分析服务。该软件不仅支持通过关键词进行搜索抓取&#xff0c;还能够通过分享链接进行单个视频的抓取和下载&#xff0c;让用户轻松获取抖音视频评论数据。 其中&#xff0c…

FL Studio All Plugins Edition2024中文完整版Win/Mac

FL Studio All Plugins Edition&#xff0c;常被誉为数字音频工作站&#xff08;DAW&#xff09;的佼佼者&#xff0c;是音乐制作人和声音工程师钟爱的工具。它集音频录制、编辑、混音以及MIDI制作为一体&#xff0c;为用户提供了从创作到最终作品输出的完整工作流程。这个版本…

学习python的第7天,她不再开放她的听歌榜单

我下午登录上小号&#xff0c;打开聊天消息看到了她的回复&#xff0c;我很开心兴奋&#xff0c;可是她不再开放她的听歌榜单了&#xff0c;我感觉得到&#xff0c;我要失恋了。 “因为当年电视上看没有王菲版本的” “行”。 “那你以后还会开放听歌榜单吗&#xff1f;”我…

[electron]官方示例解析

官方例子 github链接 main.js const { app, BrowserWindow } require(electron)说句实话这里的语法是有部分看不懂的。导入模块虽然electron有很多模块。但是这里只是用到了app 和 BrowserWindow function createWindow () {// Create the browser window.const mainWindo…

ESP32(VSCode+PlatformIO)开发环境搭建教程(2024版)

目录 一、安装vscode&#xff1a;[点击下载](https://code.visualstudio.com/Download)二、安装Python环境三、安装VSCode platformio插件四、使用PlatformIO创建项目五、编译下载 一、安装vscode&#xff1a;点击下载 二、安装Python环境 本文以Win11系统做演示&#xff0c;其…

【踩坑】修复报错 recursion is detected during loading of “cv2“ binary extensions

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] 报错如下&#xff1a; 修复方法一&#xff1a; pip install pyinstaller5.9 修复方法二&#xff1a; pip install opencv-python4.5.3.56
推荐文章