当前位置: 首页 > news >正文

【动态规划】题目中的「0-1 背包」和「完全背包」的问题


🎒 先看「0-1 背包」(对比理解)

想象你去超市,有一个购物袋(容量有限),面前有几件商品:

商品体积价值
苹果23
香蕉34
橙子45

规则:每种商品只能拿 1 个
问:在袋子不超重的前提下,怎么拿能让总价值最大?

✅ 这就是 0-1 背包:每个物品 选 or 不选(0 或 1 次)


🛒 再看「完全背包」

现在换一家店,规则变了:

每种商品可以拿任意多个(只要袋子装得下)!

比如苹果,你可以拿 1 个、2 个、3 个……直到袋子满了。

问题还是:怎么拿能让总价值最大?

✅ 这就是 完全背包:每个物品 可以选无限次


🔁 关键区别

类型每个物品能选几次?例子
0-1 背包最多 1 次打家劫舍(每间房子只能偷/不偷一次)
完全背包无限次零钱兑换(硬币可以重复用)、完全平方数(4 可以用多次)

🧩 回到你的代码:为什么是「完全背包」?

问题:用最少的完全平方数(1, 4, 9, 16…)凑出 n

  • 平方数就像“硬币”:1, 4, 9, …
  • 你可以用 多个 4(比如 4+4+4=12)
  • 目标不是“最大价值”,而是“最少个数

所以这是一个 “完全背包求最小数量” 的变种!

💡 类比:你有面值为 1、4、9、16… 的硬币,要凑出金额 n,问最少用几枚硬币?
→ 这就是经典的 “零钱兑换 II” 的最小硬币数版本,属于完全背包。


📌 完全背包的两个核心特征

  1. 物品可重复使用
    → 在递归中,选了之后,还能继续选同一个物品
    → 你的代码中:dfs(i, j - i*i) + 1i 没变,说明下次还能选

  2. 状态转移时,顺序很重要(DP写法中)

    • 0-1 背包:倒序遍历容量(避免重复选)
    • 完全背包:正序遍历容量(允许重复选)

但在你的记忆化搜索写法中,这一点体现在:选了之后不减 i


🌰 再举个完全背包的例子:零钱兑换

硬币:[1, 3, 4]
目标:6
最少硬币数?

可能方案:

  • 3 + 3 → 2 枚 ✅(最优)
  • 4 + 1 + 1 → 3 枚
  • 1×6 → 6 枚

这里 3 用了两次,所以是完全背包。

对应的递归思路:

min(dfs(3,3) + 1,dfs(不用3, 用更小的) 
)

而且 dfs(用3, 剩3) 里还能继续用 3!


✅ 总结:一句话记住

完全背包 = 物品可以“无限拿”,选了一个之后,下次还能选它。

而你的代码中:

  • “物品” = 完全平方数(1, 4, 9…)
  • “无限拿” = 递归时 i 不减(dfs(i, j - i*i)
  • “最少个数” = 用 min 而不是 max

这就是为什么它属于完全背包问题


举例

【279. 完全平方数】

给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。

# 写在外面,多个测试数据之间可以共享,减少计算量
@cache  # 缓存装饰器,避免重复计算 dfs 的结果(记忆化)
def dfs(i: int, j: int) -> int:if i == 0:return inf if j else 0if j < i * i:return dfs(i - 1, j)  # 只能不选return min(dfs(i - 1, j), dfs(i, j - i * i) + 1)  # 不选 vs 选class Solution:def numSquares(self, n: int) -> int:return dfs(isqrt(n), n)

我们用刚刚理解的 「完全背包」视角,解读这段代码,确保每一步都清晰、有逻辑、不跳步。


🎯 问题再明确(完全背包视角)

你有无限量的“平方数硬币”:1, 4, 9, 16, 25, …
目标:凑出金额 n
要求:用最少的硬币数量

这和「零钱兑换」一模一样,只是硬币面值固定为平方数。


🧠 动态规划的核心:状态定义

我们要定义一个函数,能回答:

“如果我只允许用前 k 种硬币(即 1², 2², …, k²),凑出金额 j,最少需要多少枚?”

在你的代码中,这个函数是:

def dfs(i: int, j: int) -> int:
  • i最大可用的硬币是 (相当于“前 i 种硬币”)
  • j:要凑的目标金额
  • 返回值:最少硬币数

✅ 所以 dfs(i, j) = 用 {1², 2², …, i²} 这些硬币(每种无限用),凑出 j 的最少个数。


🔁 状态转移:选 or 不选当前硬币?

当前最大硬币是 。面对它,你有两个选择:

✅ 选择 1:不用 这种硬币
  • 那就只能用更小的硬币:{1², …, (i-1)²}
  • 问题变成:dfs(i - 1, j)
✅ 选择 2:用一个 硬币
  • 花掉 ,剩下金额是 j - i²
  • 关键:因为硬币无限用,下次还能继续用
  • 所以问题变成:dfs(i, j - i²) + 1(+1 是因为用了一枚)

🎯 这就是完全背包的标志:选了之后,状态中的“可用硬币范围”不变(还是 i)
(对比 0-1 背包:选了之后要变成 i-1

然后,我们选这两种方案中更优的(数量更少的)

min( dfs(i-1, j), dfs(i, j - i*i) + 1 )

⚠️ 但要注意前提:能不能选?

你不能选一个比目标还大的硬币!

  • 如果 j < i*i,说明 太大了,不能选
  • 所以只能走“不选”的分支:
    if j < i * i:return dfs(i - 1, j)
    

🚧 边界条件:递归的终点

什么时候停止递归?

  • i == 0:表示没有硬币可用(因为最小硬币是 1²,i=0 对应 0²=0,没用)
    • 如果目标 j == 0:不需要硬币 → 返回 0
    • 如果目标 j > 0:没法凑 → 返回 无穷大(inf),表示“不可能”
if i == 0:return inf if j else 0

💡 infmin() 中会被自动忽略,所以不会影响结果。


🚀 初始调用:从哪里开始?

我们要凑 n,最大可能用到的硬币是 不超过 n 的最大平方数

  • 比如 n=12,最大平方数是 9 = 3²
  • 所以 i 的初始值 = isqrt(n)(整数平方根)
return dfs(isqrt(n), n)

这样既不会漏掉可能的硬币,也不会考虑无用的大硬币(比如 16>12)。


🧪 完整例子:n = 12

硬币可选:1, 4, 9(因为 isqrt(12)=3)

调用:dfs(3, 12)

dfs(3,12)
├─ 不用9 → dfs(2,12)  → 用1,4凑12 → 最少3个(4+4+4)
└─ 用9 → dfs(3,3)+1   → 用1,4,9凑3 → 只能用1(3个)→ 3+1=4
→ min(3,4) = 3 ✅

dfs(2,12) 内部:

dfs(2,12)  // 用1,4凑12
├─ 不用4 → dfs(1,12) → 12个1 → 12
└─ 用4 → dfs(2,8)+1├─ 用4 → dfs(2,4)+1│    └─ 用4 → dfs(2,0)+1 = 0+1=1└─ 所以 dfs(2,4)=1 → dfs(2,8)=2 → dfs(2,12)=3

完全符合“完全背包”逻辑!


🧩 为什么叫“背包”?

  • “背包容量” = 目标金额 j
  • “物品” = 平方数硬币
  • “物品重量” = 硬币面值(i²)
  • “物品价值” = 1(因为我们只关心数量,每个硬币算1个)
  • “目标” = 装满背包(凑出 j),且物品总数最少

✅ 最终总结(完全背包视角)

代码元素完全背包含义
i当前考虑的最大硬币面值是
j背包剩余容量(要凑的金额)
dfs(i-1, j)不选 硬币
dfs(i, j - i*i) + 1选一个 硬币(还能再选!)
j < i*i硬币太大,不能选
i == 0没有硬币了
@cache避免重复计算相同子问题
isqrt(n)最大可能用到的硬币

它本质上就是一个用递归+记忆化实现的完全背包求最小值问题

http://www.dtcms.com/a/482706.html

相关文章:

  • Streamlit 中文全面教程:从入门到精通
  • 大模型系列-dify
  • 推荐系统:Python汽车推荐系统 数据分析 可视化 协同过滤推荐算法 汽车租赁 Django框架 大数据 计算机✅
  • 第16讲:深入理解指针(6)——sizeof vs strlen 与 指针笔试题深度解析
  • 【iOS】PrivacyInfo.xcprivacy隐私清单文件(二)
  • 环保网站建设公司排名手机访问wordpress网站卡
  • 从零构建大模型 Build a large language model from scratch by Sebastian Raschka 阅读笔记
  • 基于Chainlit和Llamalndex的智能RAG聊天机器人实现详解
  • 18.5 GLM-4大模型私有化部署实战:3秒响应+显存降低40%优化全攻略
  • Prisma 命令安全指南
  • Linux系统下文件操作系统调用详解
  • 网站备案后需要年检吗官方网站搭建
  • 515ppt网站建设北京朝阳区属于几环
  • 5~20.数学基础
  • HTML应用指南:利用POST请求获取全国鸿蒙智行门店位置信息
  • 优先级队列(堆)-295.数据流的中位数-力扣(LeetCode)
  • 大语言模型推理本质与技术演进
  • 福田区网站建最牛视频网站建设
  • 踩坑实录:Go 1.25.x 编译的 exe 在 Windows 提示“此应用无法运行”
  • 学习网站建设有前景没wordPress登不上数据库
  • 互联网大厂Java面试:从缓存技术到安全框架的深度探索
  • 本地部署开源集成工具 Jenkins 并实现外网访问( Linux 版本)
  • HackerNews 播客生成器
  • 新网站优化品牌营销策略四种类型
  • Linux 命令:umount
  • springboot159基于springboot框架开发的景区民宿预约系统的设计与实现
  • LatchUtils:简化Java异步任务同步的利器
  • 数据库设计基础知识(3)关系运算
  • uniapp 编译支付宝小程序canvas 合成图片实例,支付宝小程序 canvas 渲染图片 可以换成自己的图片即可
  • jmeter环境搭建