【动态规划之完全背包问题】在实际问题中优化背包模型以及无效化情况的处理
创始人
2024-04-01 18:23:21
0

⭐️前面的话⭐️

本篇文章将介绍动态规划中的背包问题——完全背包问题,前面我们已经介绍了什么是完全背包问题以及对应的解决方案以及练习,本文将列举一道实际问题来强化对完全背包的一维优化思维和无效化状态的处理。

📒博客主页:未见花闻的博客主页
🎉欢迎关注🔎点赞👍收藏⭐️留言📝
📌本文由未见花闻原创,CSDN首发!
📆首发时间:🌴2022年10月29日🌴
✉️坚持和努力一定能换来诗与远方!
💭推荐书籍:📚《算法》,📚《算法导论》
💬参考在线编程网站:🌐牛客网🌐力扣
博主的码云gitee,平常博主写的程序代码都在里面。
博主的github,平常博主写的程序代码都在里面。
🍭作者水平很有限,如果发现错误,一定要及时告知作者哦!感谢感谢!


📌导航小助手📌

  • ⭐️【一维优化强化以及无效状态】零钱兑换⭐️
    • 🔐题目详情
    • 💡解题思路与代码
      • 🍭朴素解法
      • 🍭滚动数组优化空间
      • 🍭一维数组优化空间
  • 🌱总结


1


⭐️【一维优化强化以及无效状态】零钱兑换⭐️

🔐题目详情

322. 零钱兑换

难度中等

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1

你可以认为每种硬币的数量是无限的。

示例 1:

输入:coins = [1, 2, 5], amount = 11
输出:3 
解释:11 = 5 + 5 + 1

示例 2:

输入:coins = [2], amount = 3
输出:-1

示例 3:

输入:coins = [1], amount = 0
输出:0

提示:

  • 1 <= coins.length <= 12
  • 1 <= coins[i] <= 231 - 1
  • 0 <= amount <= 104

💡解题思路与代码

🍭朴素解法

首先我们来分析一下题目,题目给了我们很多硬币有各种面值,每种硬币可以选择无数次,需要求这些硬币和恰好为amount的最小所选硬币数。

这里我们可以将每一种硬币视为一种【物品】,amount可以视为背包的容量,硬币的面值可以看做物品的【体积】,所用的硬币数我们可以理解为【总价值】,则每种硬币的【价值】就是1,这样我们就能够将这个问题视作我完全背包问题。

状态定义: 不妨定义f[i][j]f[i][j]f[i][j]表示从前iii种硬币中选择,恰好凑成jjj,所选物品的最小数量,由于硬币的面值是不定的,所以可能选择若干硬币可能凑不出amount,所以我们需要定义一个无效值来表示这个状态是无效的,也就是无法凑出amount的,由于我们求的是最小的硬币数,所以我们选择的无效值要比较大,可以选择Integer.MAX_VALUE

确定初始状态: 当i=0i=0i=0时,只能选择第一个硬币,那么就尽量选,能凑成jjj,就加上所选的硬币个数,否则就将该状态赋值为无效值。

状态转移方程推导: 当我们选择第iii种硬币的时候,我们可以选择0,1,2...k0,1,2...k0,1,2...k枚,其中kkk是最大能够选择的硬币数,即在恰好凑出jjj的情况下。
假设第iii件硬币的数值【体积】为ttt。
当我们不选择第iii件硬币时,即k=0k=0k=0,f[i][j]=f[i−1][j]f[i][j]=f[i-1][j]f[i][j]=f[i−1][j]。
当我们选择111件第iii件硬币时,即k=1k=1k=1,f[i][j]=f[i−1][j−t]+1f[i][j]=f[i-1][j - t] + 1f[i][j]=f[i−1][j−t]+1。
当我们选择222件第iii件硬币时,即k=2k=2k=2,f[i][j]=f[i−1][j−2∗t]+2f[i][j]=f[i-1][j - 2 * t] + 2f[i][j]=f[i−1][j−2∗t]+2。

当我们选择kkk件第iii件硬币时,即k=kk=kk=k,f[i][j]=f[i−1][j−k∗t]+kf[i][j]=f[i-1][j - k * t]+kf[i][j]=f[i−1][j−k∗t]+k。

我们所要求的是最小的硬币数量,所以取以上所有情况的最小值即可。

综上所述,我们的状态转移方程就出来了,当能够凑出jjj时,不妨记当前所选硬币的数量为:

f[i][j]=min(f[i−1][j−k∗t]+k),k=0,1,...f[i][j]=min(f[i-1][j-k*t]+k),k=0,1,... f[i][j]=min(f[i−1][j−k∗t]+k),k=0,1,...

但是选择kkk个硬币还是无法凑出`jjj时,该状态为无效值:

f[i][j]=INFf[i][j]=INF f[i][j]=INF

实现代码:

class Solution {//无效值private int INF = 0x3f3f3f;public int coinChange(int[] coins, int amount) {//获取硬币的个数int n = coins.length;int[][] f = new int[n][amount + 1];//确定初始状态for (int j = 1; j <= amount; j++) {int k = j / coins[0];if (k * coins[0] != j) f[0][j] = INF;else f[0][j] = k;}//状态转移for (int i = 1; i < n; i++) {int t = coins[i];for (int j = 0; j <= amount; j++) {//不选f[i][j] = f[i - 1][j];// 选k个for (int k = 1; j >= k * t; k++) {if (f[i - 1][j - k * t] != INF) {f[i][j] = Math.min(f[i][j], f[i - 1][j - k * t] + k);}}}}return f[n - 1][amount] == INF ? -1 : f[n - 1][amount];}
}

时间复杂度:O(amount2∗n)O(amount^2*n)O(amount2∗n),kkk的值不会大于amountamountamount,因为ttt最小为111,最多选择的数的数量不会超过amountamountamount。
空间复杂度:O(n∗amount)O(n*amount)O(n∗amount)

🍭滚动数组优化空间

根据观察我们知道第iii行的状态仅依赖与第i−1i-1i−1行的状态,因此我们可以使用滚动数组进行优化。

实现代码:

class Solution {//无效值private int INF = 0x3f3f3f;public int coinChange(int[] coins, int amount) {//int n = coins.length;int[][] f = new int[2][amount + 1];//确定初始状态for (int j = 1; j <= amount; j++) {int k = j / coins[0];if (k * coins[0] != j) f[0][j] = INF;else f[0][j] = k;}//状态转移for (int i = 1; i < n; i++) {int t = coins[i];int pre = (i - 1) & 1;int cur = i & 1;for (int j = 0; j <= amount; j++) {//不选f[cur][j] = f[pre][j];// 选k个for (int k = 1; j >= k * t; k++) {if (f[pre][j - k * t] != INF) {f[cur][j] = Math.min(f[cur][j], f[pre][j - k * t] + k);}}}}return f[(n - 1) & 1][amount] == INF ? -1 : f[(n - 1) & 1][amount];}
}

对于时空复杂度,只是优化了空间而已,所以时间复杂度不发生改变,空间复杂度优化到O(amount)O(amount)O(amount)。
时间复杂度:O(amount2∗n)O(amount^2*n)O(amount2∗n),kkk的值不会大于amountamountamount,因为ttt最小为111,最多选择的数的数量不会超过amountamountamount。
空间复杂度:O(amount)O(amount)O(amount)

🍭一维数组优化空间

首先我们对状态转移方程进行一个简单的推导:

f[i][j]=min(f[i−1][j],f[i−1][j−t]+1,f[i−1][j−2∗t]+2,...,f[i−1][j−kt]+k)f[i][j]=min(f[i-1][j],f[i-1][j-t]+1,f[i-1][j-2*t]+2,...,f[i-1][j-kt]+k) f[i][j]=min(f[i−1][j],f[i−1][j−t]+1,f[i−1][j−2∗t]+2,...,f[i−1][j−kt]+k)

f[i][j−t]=min(f[i−1][j−t],f[i−1][j−2t]+1,f[i−1][j−3t]+2...,f[i−1][j−kt]+k−1)f[i][j-t]=min(f[i-1][j-t],f[i-1][j-2t]+1,f[i-1][j-3t]+2...,f[i-1][j-kt]+k-1) f[i][j−t]=min(f[i−1][j−t],f[i−1][j−2t]+1,f[i−1][j−3t]+2...,f[i−1][j−kt]+k−1)

其中k∗t<=jk*t<=jk∗t<=j。

通过观察上面两个状态的式子,我们发现后面一部分式子是差了一个111如下图:

1

所以我们可以进一步优化状态转移方程,即:

f[i][j]=min(f[i−1][j],f[i][j−t]+1)f[i][j]=min(f[i-1][j],f[i][j-t]+1) f[i][j]=min(f[i−1][j],f[i][j−t]+1)

对于新推导出来的状态转移方程,它的状态转移仅仅只依赖与上一行同列状态与同一行元素前面的元素,所以我们可以将原来的二维数组优化为一维,由于它只依赖左边与正上方的元素,我们可以采取从小到大遍历背包容量状态来求背包中所放物品最大值。

1

只保留【凑出amount】维度,状态转移方程为:

f[j]=max(f[j],f[j−t]+1)f[j]=max(f[j],f[j-t]+1) f[j]=max(f[j],f[j−t]+1)

前面我们定义了一个INF,取值为整型的最大值,其实我们可以预留一些空间,就是取一个比最大值小一点的数,这样在就给了这个无效值累加的空间,不会出现越界变为负数的问题,这样我们就可以不用在进行判断前一个依赖的状态是否是INF

实现代码:

class Solution {private static final int INF = 0x3f3f3f3f;public int coinChange(int[] coins, int amount) {int n = coins.length;int[] f = new int[amount + 1];//确定初始状态for (int j = 1; j <= amount; j++) {int k = j / coins[0];if (k * coins[0] != j) f[j] = INF;else f[j] = k;}for (int i = 0; i < n; i++) {int t = coins[i];for (int j = t; j <= amount; j++) {f[j] = Math.min(f[j], f[j - t] + 1);}}return f[amount] == INF ? -1 : f[amount];}
}

时间复杂度:O(amount∗n)O(amount*n)O(amount∗n)
空间复杂度:O(amount+1)O(amount+1)O(amount+1)

🌱总结

本篇文章介绍了【完全背包】的运用,使用该模型运用到实际的问题当中,强化一维优化过程的推导。

对于完全背包的优化,相比于0-1背包问题的优化,形式上,我们只需要将 01 背包问题的「一维空间优化」解法中的「容量维度」遍历方向从「从大到小 改为 从小到大」就可以解决完全背包问题。
但本质是因为两者进行状态转移时依赖了不同的格子:
0 -1 背包依赖的是「上一行正上方的格子」和「上一行左边的格子」。
完全背包依赖的是「上一行正上方的格子」和「本行左边的格子」。


参考资料: 宫水三叶背包问题

觉得文章写得不错的老铁们,点赞评论关注走一波!谢谢啦!

1-99

相关内容

热门资讯

银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...