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

这篇具有很好参考价值的文章主要介绍了【算法 - 动态规划】找零钱问题Ⅲ。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

  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 此题,链表无敌!!!文章来源地址https://www.toymoban.com/news/detail-845492.html

到了这里,关于【算法 - 动态规划】找零钱问题Ⅲ的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 零钱兑换,凑零钱问题,从暴力递归到动态规划(java)

    322 零钱兑换 - 可以打开链接测试 给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。 计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。 你可以认为每种硬币的数量是无限的。 示例 1:

    2024年02月07日
    浏览(37)
  • LeetCode518. 零钱兑换 II 以及 动态规划相关的排列组合问题

    一、题目 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带符号整数。 示例 1: 示

    2024年02月09日
    浏览(42)
  • 【LeetCode动态规划#08】完全背包问题实战与分析(零钱兑换II)

    力扣题目链接(opens new window) 给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。 示例 1: 输入: amount = 5, coins = [1, 2, 5] 输出: 4 解释: 有四种方式可以凑成总金额: 5=5 5=2+2+1 5=2+1+1+1 5=1+1+1+1+1 示例 2: 输入: amount = 3

    2023年04月19日
    浏览(44)
  • 算法 DAY44 动态规划6 完全背包 518. 零钱兑换 II 377. 组合总和 Ⅳ

    有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。 完全背包和01背包问题唯一不同的地方就是,每种物品有无限件。 动规五步曲来分

    2024年02月01日
    浏览(52)
  • 算法 DAY45 动态规划07 70. 爬楼梯 322. 零钱兑换 279. 完全平方数 139. 单词拆分 多重背包

    和377. 组合总和 Ⅳ (opens new window)基本就是一道题了。 本题代码不长,题目也很普通,但稍稍一进阶就可以考察完全背包 动态规划五部曲 1、确定dp[j]的含义 dp[j] 凑成 j 的最少硬币的个数 2、确定递推公式 比如想凑成3, 如果手里有1,那么最小个数就是dp[2]+1 如果手里有2,那

    2024年02月02日
    浏览(59)
  • java算法day45 | 动态规划part07 ● 70. 爬楼梯 (进阶) ● 322. 零钱兑换 ● 279.完全平方数

    题目描述: 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬至多m (1 = m n)个台阶。你有多少种不同的方法可以爬到楼顶呢? 注意:给定 n 是一个正整数。 输入描述:输入共一行,包含两个正整数,分别表示n, m 输出描述:输出一个整数,表示爬到楼顶的方法数

    2024年04月14日
    浏览(54)
  • 算法打卡day39|动态规划篇07| Leetcode 70. 爬楼梯(进阶版)、322. 零钱兑换、279.完全平方数

    Leetcode 70. 爬楼梯(进阶版) 题目: 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬至多m (1 = m n)个台阶。你有多少种不同的方法可以爬到楼顶呢? 注意:给定 n 是一个正整数。 输入描述:输入共一行,包含两个正整数,分别表示n, m 输出描述:输出一个整数,

    2024年04月14日
    浏览(65)
  • 【LeetCode动态规划#08】完全背包问题实战与分析(零钱兑换II--求组合、组合总和IV--求排列)

    力扣题目链接(opens new window) 给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。 示例 1: 输入: amount = 5, coins = [1, 2, 5] 输出: 4 解释: 有四种方式可以凑成总金额: 5=5 5=2+2+1 5=2+1+1+1 5=1+1+1+1+1 示例 2: 输入: amount = 3

    2023年04月19日
    浏览(39)
  • 【动态规划】322. 零钱兑换

    定义 要凑出金额n 至少要dp(coins,n)个硬币 确定base case 目标金额为0 返回0 确定 状态 也就是原问题和子问题中的变量,你每次抽取一个硬币,都会导致目标金额减少,所以状态就是目标金额 确定选择,也就是导致状态产生变化的行为,每次选一个硬币都会导致目标金额的减少

    2024年02月10日
    浏览(43)
  • 【面试经典150 | 动态规划】零钱兑换

    【动态规划】【数组】 322. 零钱兑换 定义状态 dp[i] 表示凑成总金额的最少硬币个数。 状态转移 从小到大枚举要凑成的金额 i ,如果当前的金额可以使用面额数组中的某个面额 coin 凑成总金额的一部分,则可以更新 d p [ i ] = m i n ( d p [ i ] , d p [ i − c o i n ] + 1 ) dp[i] = min(dp[i

    2024年04月16日
    浏览(70)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包