完全背包问题全面解析:状态转移推导、正序空间优化与零钱兑换双变体(Hello 算法)

发布时间:2026/11/5 12:43:33

完全背包问题全面解析:状态转移推导、正序空间优化与零钱兑换双变体(Hello 算法) 完全背包问题全面解析状态转移推导、正序空间优化与零钱兑换双变体Hello 算法【免费下载链接】hello-algo《Hello 算法》动画图解、一键运行的数据结构与算法教程。支持简中、繁中、English、日本語提供 Python, Java, C, C, C#, JS, Go, Swift, Rust, Ruby, Kotlin, TS, Dart 等代码实现项目地址: https://gitcode.com/GitHub_Trending/he/hello-algo本文围绕《Hello 算法》完全背包问题一节对应俄语版 unbounded_knapsack_problem.md展开先厘清完全背包与 0-1 背包在物品可重复选取上的本质差异再推导二维dp状态转移方程、讲解一维空间优化为何必须正序遍历最后把同一套框架套用到零钱兑换最小硬币数与零钱兑换 II组合方案数两个典型变体上。读完你将掌握背包类 DP 的建模三步骤定状态、推转移、定边界能徒手区分反向遍历0-1与正向遍历完全背包的适用场景并可直接运行仓库内多语言代码验证结论。问题定义物品无限次的完全背包!!! question 给定 $n$ 个物品第 $i$ 个物品的重量为 $wgt[i-1]$、价值为 $val[i-1]$另有一个容量为 $cap$ 的背包。每个物品可以被选取任意多次求在不超过背包容量的前提下能装入的最大总价值。与 0-1 背包问题 相比唯一区别就是物品数量不受限。图 1 给出了书中配套的示例数据注意图中物品以索引 1 开头对应数组下标需减 1整个问题可以抽象为第 $i$ 个物品在决策时要么不拿要么拿一件。关键差异在于0-1 背包物品 $i$ 只有 1 件放入背包后只能继续从 $[i-1, c-wgt[i-1]]$ 状态递推即不再考虑它完全背包物品 $i$ 有无限件放入一件后仍然可以从前 $i$ 个物品中继续选择于是状态落到 $[i, c-wgt[i-1]]$。这一字之差正是推导全部差异的源头。状态定义与转移方程只改一处 i-1 → i沿用 0-1 背包的建模套路状态$[i, c]$表示从前 $i$ 个物品中选取、背包容积为 $c$ 时的最大价值记为 $dp[i, c]$决策对物品 $i$ 有两种选择状态的两种变化方式是不取物品 $i$容量不变转移到 $[i-1, c]$价值继承 $dp[i-1, c]$取一件物品 $i$容量消耗 $wgt[i-1]$、价值累加 $val[i-1]$转移到 $[i, c-wgt[i-1]]$价值为 $dp[i, c-wgt[i-1]] val[i-1]$。于是得到转移方程$$ dp[i, c] \max(dp[i-1, c], dp[i, c - wgt[i-1]] val[i-1]) $$边界条件没有物品$i0$或容量为 0$c0$时最大价值均为 0因此第一行与第一列初始化为 0若 $wgt[i-1] c$放不下只能执行不取分支。按 $i$、$c$ 递增的正序双层循环即可完成填表时间与空间复杂度均为 $O(n \times cap)$。仓库中的 Python 实现位于 unbounded_knapsack.pydef unbounded_knapsack_dp(wgt: list[int], val: list[int], cap: int) - int: 完全背包动态规划 n len(wgt) # 初始化 dp 表 dp [[0] * (cap 1) for _ in range(n 1)] # 状态转移 for i in range(1, n 1): for c in range(1, cap 1): if wgt[i - 1] c: # 若超过背包容量则不选物品 i dp[i][c] dp[i - 1][c] else: # 不选和选物品 i 这两种方案的较大值 dp[i][c] max(dp[i - 1][c], dp[i][c - wgt[i - 1]] val[i - 1]) return dp[n][cap]代码文件内置了驱动用例wgt [1, 2, 3]、val [5, 11, 15]、cap 4每个物品重量 1/2/3、价值 5/11/15容量 4可推算最优解为取两件重量 2 的物品总价值 22。在仓库根目录直接运行验证python3 codes/python/chapter_dynamic_programming/unbounded_knapsack.py空间优化为什么完全背包要正序遍历与 0-1 背包一样二维dp可以压缩为一维数组。区别在于遍历方向恰好相反0-1 背包的状态依赖上一行的 $[i-1, c-wgt[i-1]]$。若一维数组从左到右更新左侧的 $dp[c-wgt[i-1]]$ 已在本轮被覆盖等效于把同一物品用了多次因此必须**从右到左倒序**遍历保证每件物品至多取一次完全背包需要允许再次取同一物品其转移依赖同一行左侧的 $dp[i, c-wgt[i-1]]$所以恰恰必须**从左到右正序**遍历——更新 $dp[c]$ 时读到的是本轮已经允许重复选取的状态从而自然实现无限件。这正是两个问题在压缩后代码上仅存的差别。对照参见同一章节的 knapsack_problem.md0-1 背包一节对倒序遍历的论证。压缩后的状态转移过程可参考书中图集unbounded_knapsack_dp_comp_step1.png至unbounded_knapsack_dp_comp_step6.png位于 unbounded_knapsack_problem.assets逐帧展示 $dp$ 一维数组如何被覆盖演进。实现只需去掉第一维并把内层循环改成for c in range(1, cap 1)正序遍历def unbounded_knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) - int: 完全背包空间优化后的动态规划 n len(wgt) # 初始化 dp 表 dp [0] * (cap 1) # 状态转移 for i in range(1, n 1): # 正序遍历 for c in range(1, cap 1): if wgt[i - 1] c: dp[c] dp[c] # 若超过背包容量则不选物品 i else: dp[c] max(dp[c], dp[c - wgt[i - 1]] val[i - 1]) return dp[cap]优化后空间复杂度由 $O(n \times cap)$ 降至 $O(cap)$时间复杂度仍为 $O(n \times cap)$。该dp_comp版本同样在同目录 unbounded_knapsack.py 内两种算法使用同一组用例并打印相同的最优价值。变体一零钱兑换求最少硬币数!!! question 给定 $n$ 种硬币第 $i$ 种面额为 $coins[i-1]$目标金额为 $amt$每种硬币可取无限枚。求凑出目标金额所需的最少硬币数若无法凑出返回 $-1$。图 2 给出了配套示例面额 1、2、5 三种硬币目标金额 11最优为1 5 5共 3 枚。与完全背包的映射关系零钱兑换是求最小化的完全背包特例对应关系与差异可整理为完全背包零钱兑换物品硬币物品重量 $wgt$硬币面额 $coins$背包容积 $cap$目标金额 $amt$最大化总价值最小化硬币数目标相反不超过容量即可必须恰好凑出金额精确匹配三步建模Step 1 · 定义状态子问题为用前 $i$ 种硬币恰好凑出金额 $a$ 所需的最少硬币数记作 $dp[i, a]$二维表尺寸为 $(n1) \times (amt1)$。Step 2 · 推导转移相比完全背包有两处变化——优化目标从max换为min每取一枚硬币数加 1而非加价值 $val$$$ dp[i, a] \min(dp[i-1, a], dp[i, a - coins[i-1]] 1) $$Step 3 · 确定边界$a 0$ 时不需要任何硬币因此整个首列 $dp[i, 0] 0$$i 0$没有硬币时凑不出任何正金额属于非法解应把整个首行 $dp[0, a]$ 置为 $\infty$让 $\min()$ 自动将其淘汰。用 amt1 哨兵规避整数溢出多数语言无法在int中表示 $\infty$若直接用int最大值转移中的1可能造成溢出。书中给出的工程化做法是用amt 1充当非法解标记——因为凑出金额 $amt$ 理论上最多也只需要 $amt$ 枚硬币面额为 1 时任何超过 $amt$ 的数值都必然代表无解。返回前检查 $dp[n, amt]$ 是否仍等于 $amt1$若是则返回 $-1$。参考实现见 coin_change.pyMAX amt 1贯穿始终空间优化版coin_change_dp_comp先整体填充MAX再单独令dp[0] 0def coin_change_dp_comp(coins: list[int], amt: int) - int: 零钱兑换空间优化后的动态规划 n len(coins) MAX amt 1 dp [MAX] * (amt 1) dp[0] 0 for i in range(1, n 1): # 正序遍历 for a in range(1, amt 1): if coins[i - 1] a: dp[a] dp[a] # 若超过目标金额则不选硬币 i else: dp[a] min(dp[a], dp[a - coins[i - 1]] 1) return dp[amt] if dp[amt] ! MAX else -1注意这里空间压缩后内层同样是正序遍历——这正是硬币可无限取的语义要求与完全背包一脉相承。填表过程可对照书中coin_change_dp_step1.png~coin_change_dp_step15.png的 15 帧图解同目录 assets 下。驱动用例为coins [1, 2, 5]、amt 4运行结果应返回 22 2python3 codes/python/chapter_dynamic_programming/coin_change.py变体二零钱兑换 II求组合方案数!!! question 给定 $n$ 种硬币面额为 $coins[i-1]$目标金额为 $amt$每种硬币可取无限枚。求凑出目标金额的不同硬币组合数。转移方程max/min 换成求和状态改为用前 $i$ 种硬币恰好凑出金额 $a$ 的组合方案数$dp$ 仍为 $(n1) \times (amt1)$。当前状态等于不取第 $i$ 种硬币与取一枚第 $i$ 种硬币两个分支的方案数之和$$ dp[i, a] dp[i-1, a] dp[i, a - coins[i-1]] $$边界条件也随之改变$a 0$不选任何硬币即可凑出 0是一种可行方案故整个首列 $dp[i, 0] 1$$i 0$没有硬币时凑不出任何正金额整个首行 $dp[0, a] 0$。实现见 coin_change_ii.py其驱动用例coins [1, 2, 5]、amt 5对应 4 种组合11111、1112、122、5返回dp[n][amt] 4。空间优化版仍只需去掉硬币种类维度并保持正序def coin_change_ii_dp_comp(coins: list[int], amt: int) - int: 零钱兑换 II空间优化后的动态规划 n len(coins) dp [0] * (amt 1) dp[0] 1 for i in range(1, n 1): # 正序遍历 for a in range(1, amt 1): if coins[i - 1] a: dp[a] dp[a] # 若超过目标金额则不选硬币 i else: dp[a] dp[a] dp[a - coins[i - 1]] return dp[amt]两个变体的时间复杂度均为 $O(n \times amt)$空间复杂度经压缩后为 $O(amt)$。三个问题一览与延伸阅读问题目标转移算子是否精确匹配非法/边界初始值完全背包价值最大$\max$否不超过容量首行首列为 0零钱兑换硬币数最少$\min$是恰好凑出首列 0、首行 $amt1$非法解零钱兑换 II组合数最多求和是恰好凑出首列 1、首行 0三个问题共享同一套物品无限次 → 转移依赖本轮左邻状态 → 一维压缩时正序遍历的核心规律差异只体现在目标算子max/min/求和与边界初值上。仓库在 chapter_dynamic_programming 目录下为每个问题都提供了配套的多语言实现与驱动测试例如Cunbounded_knapsack.c、coin_change.cJavacoin_change.java以Math.min与Arrays.fill(dp, MAX)展示哨兵初始化写法Go / C / Rust / TypeScript 等同名文件可对照查看不同语言如何表达∞哨兵与二维数组初始化关于 0-1 背包含为何倒序遍历的完整推导可继续阅读 knapsack_problem.md三个问题在 summary.md 与 dp_solution_pipeline.md 中还有更系统的归类总结。对照实验时只需修改各驱动用例的wgt/val/cap或coins/amt即可验证任意规模输入下的边界行为例如把零钱兑换中的硬币面额改为不含 1 的集合如[2, 5]、amt 1会触发dp[n][amt] MAX分支并返回 $-1$。【免费下载链接】hello-algo《Hello 算法》动画图解、一键运行的数据结构与算法教程。支持简中、繁中、English、日本語提供 Python, Java, C, C, C#, JS, Go, Swift, Rust, Ruby, Kotlin, TS, Dart 等代码实现项目地址: https://gitcode.com/GitHub_Trending/he/hello-algo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/10/30 2:05:33

短剧AI视频工具选型指南:稳定性、一致性与工程化交付

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 12:29:32

Text to SQL系统第三层实现:复杂查询意图识别与优化

1. 项目概述Text to SQL系统作为自然语言处理与数据库技术交叉领域的重要应用,正在深刻改变着非技术人员与数据库交互的方式。这个看似简单的"输入问题,输出SQL"功能背后,隐藏着复杂的实现逻辑和技术挑战。今天我们就来拆解这个系统…

2026/11/5 12:29:32

Text-to-SQL系统架构解析与性能优化实践

1. 文本到SQL转换系统的核心价值 在数据驱动的时代,SQL作为与数据库交互的标准语言,其重要性不言而喻。但编写SQL查询需要专业技能,这成为了许多非技术用户获取数据的障碍。Text-to-SQL系统正是为解决这一痛点而生,它允许用户用自…

2026/11/5 12:29:32

钉钉办公自动化:OpenClaw Skill原理与应用

1. 项目概述 OpenClaw Skill是一个基于自动化原理设计的办公效率工具,专门针对钉钉平台的操作优化。它能模拟人工操作流程,实现消息自动收发、审批流程处理、考勤数据管理等高频办公场景的自动化。我在实际使用中发现,这个工具特别适合需要频…

2026/11/5 12:29:32

AI编程辅助工具:5个提升代码可用性的Prompt模板

1. 项目背景与核心价值 去年团队引入AI编程辅助工具时,我们经历了从"玩具demo"到"生产级应用"的艰难跨越。最深的体会是:需求沟通的质量直接决定了AI产出的可用性。那些模糊的"做个用户管理系统"之类的需求,AI…

2026/11/5 12:24:32

云原生智能体协同架构在城市轨道交通中的应用

1. 新一代城市轨道交通云原生智能体协同架构概述城市轨道交通作为现代都市的"大动脉",其运营效率和服务质量直接影响着千万市民的日常生活。近年来,随着云计算、人工智能等技术的快速发展,传统的轨道交通智能化系统架构正面临着前所…

2026/11/5 12:14:29

Stimulsoft Reports 2019.2.2 落地指南:老系统兼容与版本陷阱规避

简介:Stimulsoft Reports 2019.2.2 是一款面向.NET开发者的专业报表控件,适用于WinForms、ASP.NET及WPF平台,帮助开发者快速构建数据可视化报表与打印输出功能,尤其适合中高级C#工程师在企业级管理系统、BI看板或财务报表模块中集…

2026/11/4 12:38:23

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 0:02:07

多智能体协作系统架构设计与实践优化

1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中…

2026/11/5 0:02:07

大模型系统性入门实战指南:从原理到部署微调的全链路路线

提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#…

2026/11/5 0:02:07

鸿蒙文件系统沙箱机制解析与开发实践

1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的…

2026/11/4 12:38:23

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑