【动态规划之完全背包问题】如何将完全背包运用到实际问题,强化完全背包以及一维优化的推导
创始人
2024-03-31 19:18:26
0

⭐️前面的话⭐️

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

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


📌导航小助手📌

  • ⭐️【强化完全背包练习】完全背包原题⭐️
    • 🔐题目详情
    • 💡解题思路与代码
      • 🍭朴素解法
      • 🍭滚动数组优化空间
      • 🍭一维数组优化空间
  • 🌱总结


1


⭐️【强化完全背包练习】完全背包原题⭐️

🔐题目详情

279. 完全平方数

难度中等

给你一个整数 n ,返回 和为 n 的完全平方数的最少数量

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,14916 都是完全平方数,而 311 不是。

示例 1:

输入:n = 12
输出:3 
解释:12 = 4 + 4 + 4

示例 2:

输入:n = 13
输出:2
解释:13 = 4 + 9

提示:

  • 1 <= n <= 104

💡解题思路与代码

🍭朴素解法

我们分析一下,本题的意思是从不大于n的完全平方数中选择若干数凑成n,求凑成数n的最小数量。

那这道题就是一道完全背包问题,即背包中的【物品】就是不大于n的完全平方数,每件物品的价值可以理解为1,每件物品体积可以理解为数字的值,每个物品可以选择无数次 ,求凑成数n的所选择物品的最小数量(即凑出总体积恰好为n时的物品最低价值)。

状态定义: 不妨定义f[i][j]f[i][j]f[i][j]表示从前iii件物品中选择,恰好凑成jjj,所选物品的最小数量,任何数都是1的倍数,所以不存在无法凑出的情况。

确定初始状态: 当i=0i=0i=0时,只能选择物品【1】,因为111可以被任何正整数整除,所以f[0][j]=j/1f[0][j]=j/1f[0][j]=j/1。

状态转移方程推导: 当我们选择第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。

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

综上所述,我们的状态转移方程就出来了,不妨记当前物品的价值为:

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

实现代码:

class Solution {public int numSquares(int n) {//动态规划 完全背包问题//1.枚举1-n之间所有的完全平方数int len = (int) Math.sqrt(n);int[] nums = new int[n];for (int i = 1; i * i <= n; i++) {nums[i - 1] = i * i;}//状态定义f[i][j]表示和为n完全平方和的最小数量int[][] f = new int[len][n + 1];//确定初始状态,处理只有一个数字时for (int j = 1; j <= n; j++) {int k = j / nums[0];//由于第一个数字是1 大于1的整数一定能够被1整除,所以判断是否为1的倍数可以省略f[0][j] = k;}//状态转移for (int i = 1; i < len; i++) {int t = nums[i];for (int j = 1; j <= n; j++) {//不选f[i][j] = f[i - 1][j];//选择k个for (int k = 1; k * t <= j; k++) {f[i][j] = Math.min(f[i][j], f[i - 1][j - k * t] + k);}}}return f[len - 1][n];}
}

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

🍭滚动数组优化空间

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

实现代码:

class Solution {public int numSquares(int n) {//动态规划 完全背包问题//1.枚举1-n之间所有的完全平方数int len = (int) Math.sqrt(n);int[] nums = new int[n];for (int i = 1; i * i <= n; i++) {nums[i - 1] = i * i;}//状态定义f[i][j]表示和为n完全平方和的最小数量int[][] f = new int[2][n + 1];//确定初始状态,处理只有一个数字时for (int j = 1; j <= n; j++) {int k = j / nums[0];//由于第一个数字是1 大于1的整数一定能够被1整除,所以判断是否为1的倍数可以省略f[0][j] = k;}//状态转移for (int i = 1; i < len; i++) {int t = nums[i];int cur = i & 1;int pre = (i - 1) & 1;for (int j = 1; j <= n; j++) {//不选f[cur][j] = f[pre][j];//选择k个for (int k = 1; k * t <= j; k++) {f[cur][j] = Math.min(f[cur][j], f[pre][j - k * t] + k);}}}return f[(len - 1) & 1][n];}
}

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

🍭一维数组优化空间

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

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

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

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)

实现代码:

class Solution {public int numSquares(int n) {//动态规划 完全背包问题//1.枚举1-n之间所有的完全平方数int len = (int) Math.sqrt(n);int[] nums = new int[n];for (int i = 1; i * i <= n; i++) {nums[i - 1] = i * i;}//状态定义f[j]表示和为n完全平方和的最小数量int[] f = new int[n + 1];//确定初始状态,处理只有一个数字时for (int j = 1; j <= n; j++) {int k = j / nums[0];//由于第一个数字是1 大于1的整数一定能够被1整除,所以判断是否为1的倍数可以省略f[j] = k;}//状态转移for (int i = 1; i < len; i++) {int t = nums[i];for (int j = t; j <= n; j++) {//f[j]=min(f[j], f[j-t]+1)f[j] = Math.min(f[j], f[j - t] + 1);}}return f[n];}
}

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

🌱总结

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

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


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

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

1-99

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...