【动态规划】01背包与完全背包问题详解,LeetCode零钱兑换II秒解,轻松解力扣

【动态规划】01背包与完全背包问题详解,LeetCode零钱兑换II秒解,轻松解力扣



👨‍💻程序员三明治个人主页
🔥 个人专栏: 《设计模式精解》《重学数据结构》

🤞先做到 再看见!


目录

01背包题目分析

有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

每一件物品其实只有两个状态,取或者不取,所以可以使用回溯法搜索出所有的情况,那么时间复杂度就是O(2^n),这里的n表示物品数量。

所以暴力的解法是指数级别的时间复杂度。进而才需要动态规划的解法来进行优化!

在下面的讲解,我举一个例子:

物品为:

重量价值
物品0115
物品1320
物品2430

01背包解决方法

递归五部曲:

  1. 确定dp数组以及下标的含义:dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少

为什么需要用二维数组呢?因为有两个维度需要分别表示:物品 和 背包容量

我们先看把物品0 放入背包的情况:

再看把物品1 放入背包:

背包容量为 0,放不下物品0 或者物品1,此时背包里的价值为0。

背包容量为 1,只能放下物品0,背包里的价值为15。

背包容量为 2,只能放下物品0,背包里的价值为15。

背包容量为 3,上一行同一状态,背包只能放物品0,这次也可以选择物品1了,背包可以放物品1 或者 物品0,物品1价值更大,背包里的价值为20。

背包容量为 4,上一行同一状态,背包只能放物品0,这次也可以选择物品1了,背包可以放下物品0 和 物品1,背包价值为35。

  1. 确定递推公式

对于递推公式,首先我们要明确有哪些方向可以推导出 dp[i][j]

这里我们dp[1][4]的状态来举例:

求取 dp[1][4] 有两种情况:

  1. 放物品1
  2. 还是不放物品1

如果不放物品1, 那么背包的价值应该是 dp[0][4] 即 容量为4的背包,只放物品0的情况。

如果放物品1, 那么背包要先留出物品1的容量,目前容量是4,物品1 的容量(就是物品1的重量)为3,此时背包剩下容量为1。

容量为1,只考虑放物品0 的最大价值是 dp[0][1],这个值我们之前就计算过。

所以 放物品1 的情况 = dp[0][1] + 物品1 的价值,推导方向如图:

所以两种情况综合一起可以得出:

递归公式: <font>dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);</font>

3. dp数组初始化

首先从dp[i][j]的定义出发,如果背包容量j为0的话,即dp[i][0],无论是选取哪些物品,背包价值总和一定为0。如图:

从递归公式可以看出i 是由 i-1 推导出来,那么i为0的时候就一定要初始化。

  1. 确定遍历顺序

先遍历 物品还是先遍历背包重量呢?

其实都可以!! 但是先遍历物品更好理解

那么我先给出先遍历物品,然后遍历背包重量的代码。

for(int i =1; i < n; i++){for(int j =0; j <= bagweight; j++){if(j < weight[i]){ dp[i][j]= dp[i -1][j];}else{ dp[i][j]=Math.max(dp[i -1][j], dp[i -1][j - weight[i]]+ value[i]);}}}
  1. 举例推导dp数组

最终结果就是dp[2][4]。

完全背包题目分析

完全背包和01背包问题唯一不同的地方就是,每种物品有无限件

在下面的讲解,继续用之前的例子:

物品为:

重量价值
物品0115
物品1320
物品2430

完全背包解决方法

  1. 确定dp数组以及下标的含义

dp[i][j] 表示从下标为[0-i]的物品,每个物品可以取无限次,放进容量为j的背包,价值总和最大是多少

  1. 确定递推公式

这里依然拿dp[1][4]的状态来举例

求取 dp[1][4] 有两种情况:

  1. 放物品1
  2. 还是不放物品1

如果不放物品1, 那么背包的价值应该是 dp[0][4] 即 容量为4的背包,只放物品0的情况。

如果放物品1, 那么背包要先留出物品1的容量,目前容量是4,物品1 的容量(就是物品1的重量)为3,此时背包剩下容量为1。

容量为1,只考虑放物品0 和物品1 的最大价值是 dp[1][1], 注意 这里和01背包有所不同了

在 01背包中,背包先空留出物品1的容量,此时容量为1,只考虑放物品0的最大价值是 dp[0][1],因为01背包每个物品只有一个,既然空出物品1,那背包中也不会再有物品1

而在完全背包中,物品是可以放无限个,所以 即使空出物品1空间重量,那背包中也可能还有物品1,所以此时我们依然考虑放 物品0 和 物品1 的最大价值即: dp[1][1], 而不是 dp[0][1]

所以可以得出

递推公式: <font>dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]);</font>

  1. dp数组初始化

如果背包容量j为0的话,即dp[i][0],无论是选取哪些物品,背包价值总和一定为0

再看其他情况:<font>dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]);</font> 可以看出有一个方向 i 是由 i-1 推导出来,那么i为0的时候就一定要初始化。

dp[0][j],即:存放物品0的时候,各个容量的背包所能存放的最大价值。

for(int j = weight[0]; j <= bagWeight; j++){ dp[0][j]= dp[0][j - weight[0]]+ value[0];}
  1. 确定遍历顺序

01背包二维DP数组,先遍历物品还是先遍历背包都是可以的。

因为两种遍历顺序,对于二维dp数组来说,递推公式所需要的值,二维dp数组里对应的位置都有。

  1. 举例推导dp数组

LeetCode 518.零钱兑换II

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

请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0

假设每一种面额的硬币有无限个。

题目数据保证结果符合 32 位带符号整数。

示例 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, coins = [2] 输出:0 解释:只用面额 2 的硬币不能凑成总金额 3 。 

思路

本题求的是装满这个背包的物品组合数是多少。因为每一种面额的硬币有无限个,所以这是完全背包

动规五部曲

  1. 确定dp数组以及下标的含义:

dp[i][j]:使用 下标为[0, i]的coins[i]能够凑满j(包括j)这么大容量的包,有dp[i][j]种组合方法。

  1. 递推公式

因为本题属于完全背包问题,所以递推公式需要参考

dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i])

但考虑到我们的dp数组含义求的是组合个数,所以本题的递推公式是

dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i]]

  1. 初始化

以这个为例

第一行如何初始化?

dp[0][0] 应该是多少?

背包空间为0,装满「物品0」 的组合数有多少呢?应该是 0 个, 但如果 「物品0」 的 数值就是0呢? 岂不是可以有无限个0 组合 和为0!

题目描述中说了<font>1 <= coins.length <= 300</font><font>1 <= coins[i] <= 5000</font>,所以不用考虑 物品数值为0的情况。

dp[0][j]的含义:用「物品0」(即coins[0]) 装满 背包容量为j的背包,有几种组合方法。

如果 j 可以整除 物品0,那么装满背包就有1种组合方法。

for(int j =0; j <= amount; j++){if(j % coins[0]==0) dp[0][j]=1;}

最左列如何初始化呢?

dp[i][0] 的含义:用物品i(即coins[i]) 装满容量为0的背包 有几种组合方法。

都有一种方法,即不装。

所以 dp[i][0] 都初始化为1

综上,可以得出下图:

  1. 确定遍历顺序

先遍历物品,在遍历背包比较容易理解

  1. 打印dp数组

代码实现

classSolution{publicintchange(int amount,int[] coins){int[][] dp =newint[coins.length][amount +1];// 初始化最左列for(int i =0; i < coins.length; i++){ dp[i][0]=1;}// 初始化最上行for(int j =0; j <= amount; j++){if(j % coins[0]==0) dp[0][j]=1;}// 开始遍历for(int i =1; i < coins.length; i++){for(int j =0; j <= amount; j++){if(coins[i]> j){ dp[i][j]= dp[i -1][j];}else{ dp[i][j]= dp[i -1][j]+ dp[i][j - coins[i]];}}}return dp[coins.length -1][amount];}}




如果我的内容对你有帮助,请辛苦动动您的手指为我点赞,评论,收藏。感谢大家!!

在这里插入图片描述

Read more

队列:从数据结构到系统设计的核心利器

队列:从数据结构到系统设计的核心利器

在计算机科学的世界里,有些基础概念看似简单,却支撑起了无数复杂系统的运行——队列就是这样一种存在。它不仅是一种数据结构,更代表着一种有序处理、缓冲协调的设计思想,从底层操作系统到分布式架构,都能看到它的身影。 队列的核心价值:有序与平衡 队列的本质是一种"先进先出"(FIFO)的线性表,但它的价值远不止于此。在实际系统中,队列主要解决三类问题: 1. 顺序保证:确保数据按产生顺序被处理,这在日志处理、事件响应等场景中至关重要。 2. 缓冲削峰:当数据生产速度超过消费能力时,队列可以临时存储数据,避免系统过载。 3. 解耦协同:在分布式系统中,队列可以隔离生产者和消费者,使它们无需直接交互即可协同工作。 这些价值决定了队列从简单的数据结构升华为系统设计中的关键组件。 如何理解“队列”? 队列这个概念非常好理解。你可以把它想象成排队买票,先来的先买,后来的人只能站末尾,不允许插队。先进者先出,这就是典型的“队列”。 我们知道,栈只支持两个基本操作:入栈 push(

By Ne0inhk
【数据结构-初阶】顺序表相关习题

【数据结构-初阶】顺序表相关习题

🎈主页传送门:良木生香 🔥个人专栏:《C语言》 《数据结构-初阶》 《程序设计》 🌟人为善,福随未至,祸已远行;人为恶,祸虽未至,福已远离 上期回顾:在上一篇文章中(【数据结构-初阶】详解线性表(1)---顺序表),我们详细介绍了线性表系列第一种数据结构---顺序表,这个数据结构是以数组为底建立的,也学习了如何用线性表进行增删查改的操作,那么我们今天就用顺序表进行解题~~~   题目一:移除元素 这是题目链接:27.移除元素,下面是具体的题目与示例: 由题意知,这道题是想让我们将数组中值为val的元素删除,我们能怎么做呢? 创建新的数组?那不行,题目已经要求我们只能在原地进行操作了,就意味着不能创建新的数组来进行辅助 那该怎么办呢?简单,我们只需用上算法中最基础的---双指针算法了 我们用双指针,不一定用真的指针指向某个元素,有时也可以用下标,讲究的是一种算法思想,并没有一定的形式 我们用两个指针,刚开始都同事之下那个num数组的第一个元素,随后将其中一个指针用于遍历数组,如果两个指针指向的内容不相同,那就将内容进行交换,两个指针同时向后移动一位;如果相同

By Ne0inhk
我爱学算法之——floodfill算法(上)

我爱学算法之——floodfill算法(上)

前言 Flood Fill(也称为种子填充算法)是一种用于确定连接到多维数组中给定节点的区域的算法 核心思想 * 从起点开始:从一个初始像素(种子点)开始 * 扩散填充:向四周(通常为4方向或8方向)扩展 * 条件匹配:只填充与种子点颜色相同且相邻的像素 * 避免重复:标记已访问的位置,防止重复处理 一、图像渲染 题目解析 给定一个 m*n 的二维数组,从起始位置 [sr,sc] 开始,将从起始位置的 上下左右 四个方向上 相邻且与起始位置初始颜色相同的像素点进行染色,直到没有其他原始颜色的相邻像素。 算法思路 这道题整体还是非常简单的,从起始位置开始,进行一次深度优先遍历 DFS 即可。 注意:当起始位置[sr, sc]的颜色和目标颜色 color 相同时,直接返回原二维数组即可。 代码实现

By Ne0inhk
❿⁄₁₃ ⟦ OSCP ⬖ 研记 ⟧ 密码攻击实践 ➱ 获取并破解Net-NTLMv2哈希(下)

❿⁄₁₃ ⟦ OSCP ⬖ 研记 ⟧ 密码攻击实践 ➱ 获取并破解Net-NTLMv2哈希(下)

郑重声明:本文所涉安全技术仅限用于合法研究与学习目的,严禁任何形式的非法利用。因不当使用所导致的一切法律与经济责任,本人概不负责。任何形式的转载均须明确标注原文出处,且不得用于商业目的。 🔋 点赞 | 能量注入 ❤️ 关注 | 信号锁定 🔔 收藏 | 数据归档 ⭐️ 评论 | 保持连接💬 🌌 立即前往 👉晖度丨安全视界🚀 ▶ 信息收集  ▶ 漏洞检测 ▶ 初始立足点  ▶ 权限提升 ▶ 横向移动 ➢ 密码攻击 ➢  获取并破解Net-NTLMv2哈希(下)🔥🔥🔥 ▶ 报告/分析 ▶ 教训/修复 目录 1.密码破解 1.1 破解Windows哈希实践 1.1.3 捕获Net-NTLMv2哈希实践 1.1.3.3 使用Netcat连接绑定 Shell(kali上) 1.连接流程 2.连接命令

By Ne0inhk