用填充表格法-理解01背包及其变体问题

用填充表格法-理解01背包及其变体问题

动态规划(Dynamic Programming, DP)解决问题的核心逻辑,本质是通过填充表格 逐步推导最优解------把复杂的多阶段决策问题,转化为按规则填充表格的可视化过程。以01背包问题(最经典的DP模型)为例,我们先明确最终要填充的核心表格形态,后续所有解题步骤都是为了按规则完成这张表格,表格填完之时,就是问题解决之日。(看此文前,如果动态规划零基础,请看下DP解题的5步「钥匙」

01背包问题核心表格(空表,后续逐步填充):

前i个物品\背包容量j 0(容量为0) 1(容量为1) 2(容量为2) ...(容量递增) C(背包最大容量)
0(无物品) 待填充 待填充 待填充 待填充 待填充
1(第1个物品) 待填充 待填充 待填充 待填充 待填充
2(第2个物品) 待填充 待填充 待填充 待填充 待填充
...(物品递增) 待填充 待填充 待填充 待填充 待填充
n(第n个物品) 待填充 待填充 待填充 待填充 待填充(最终答案)

表格说明:表格中每个单元格dp[i][j]代表「前i个物品放入容量为j的背包的最大价值」,我们的目标就是按规则填充所有单元格,最终右下角dp[n][C]即为01背包问题的最优解。

要有序、正确地填充这张表格,需要遵循DP解题的5步「钥匙」------这是贯穿所有DP问题的通用拆解思路,每一步都对应表格填充的关键环节:

  1. 确定dp数组及下标的含义 :明确dp[i](或二维dp[i][j])代表什么物理意义(比如"第i阶台阶的爬法数"、"前i个物品放入容量为j的背包的最大价值");

  2. 确定递推公式 :找到dp[i]与子问题dp[i-1]/dp[i-2]等的依赖关系(这是DP的核心,本质是将大问题拆解为可复用的子问题);

  3. dp数组如何初始化:根据问题边界条件,初始化无法通过递推得到的基础值(比如"背包容量为0时价值为0"、"无物品时价值为0");

  4. 确定遍历顺序 :保证计算dp[i]时,其依赖的子问题已经被计算完成。这里可以简单理解为「表格的填充顺序」------ 二维DP类似逐行逐列填充表格,一维DP则是按特定方向(正序/倒序)填充单行表格;

  5. 打印dp数组(验证):通过打印中间结果,验证递推逻辑是否正确(这是调试DP问题的必备步骤,能快速定位递推公式或遍历顺序的错误)。

后续所有01背包及变体问题的分析,都将围绕这5步「钥匙」展开,本质就是用这5步规则完成对应表格的填充,最终通过表格得到问题答案。

一、01背包问题基础定义

01背包的核心场景:有n个物品,每个物品只有1个 (要么选、要么不选,即"0-1"选择),每个物品有对应的重量w[i]和价值v[i],现有一个容量为C的背包,如何选择物品放入背包,使得放入物品的总价值最大?

示例:物品重量weights = [2,3,4,5],价值values = [3,4,5,6],背包容量capacity = 8,求最大价值。

二、基础解法:二维DP数组

我们将严格按照DP解题的5步「钥匙」拆解二维DP解法,每一步都对应核心表格的填充规则:

2.1 步骤1:确定dp数组及下标的含义

定义二维DP数组dp[i][j]:表示「前i个物品(从第1个到第i个),放入容量为j的背包中,能获得的最大价值」。

说明:

  • 「前i个物品」不是指编号为i的物品,而是范围上的前i个(比如i=2代表前2个物品);

  • 下标i的范围:0n(i=0表示无物品),下标j的范围:0 C(j=0表示背包容量为0)。

2.2 步骤2:确定递推公式

对于第i个物品(重量w[i-1]、价值v[i-1],因数组下标从0开始),有两种选择:选或不选,递推公式由此而来:

  1. i 不选第个物品 :前i个物品的最大价值 = 前i-1个物品的最大价值,即dp[i][j] = dp[i-1][j]

  2. i 选第个物品 :需保证背包容量j ≥ w[i-1],此时价值 = 前i-1个物品放入容量j - w[i-1]的最大价值 + 第i个物品的价值,即dp[i][j] = dp[i-1][j - w[i-1]] + v[i-1]

综合两种情况,取最大值作为dp[i][j]的结果:

Plain 复制代码
dp[i][j] = max(dp[i-1][j], dp[i-1][j - w[i-1]] + v[i-1]) (当j ≥ w[i-1]时)
dp[i][j] = dp[i-1][j] (当j < w[i-1]时,容量不足,无法选第i个物品)

2.3 步骤3:dp数组如何初始化

根据边界条件初始化:

  1. i=0(无物品):无论背包容量多大,最大价值都是0,即dp[0][j] = 0j从0到C);

  2. j=0(背包容量为0):无论有多少物品,都无法放入,最大价值都是0,即dp[i][0] = 0i从0到n)。

初始化后,DP数组的第一行和第一列都为0。

2.4 步骤4:确定遍历顺序(表格填充顺序)

遍历顺序直接对应二维表格的填充顺序------即「按什么顺序逐个填写表格中的单元格」,这里有两种可行方式:

  1. 先遍历物品(i从1到n),再遍历背包容量(j从1到C);

  2. 先遍历背包容量(j从1到C),再遍历物品(i从1到n)。

两种顺序都可行,因为计算dp[i][j]时,只依赖上一行(i-1行)的结果,无论先填行还是先填列,上一行的对应位置都已提前计算完成。这就像填充一张二维表格:先遍历物品再遍历容量,是逐行填充 (每一行对应一个物品的决策,填完一行再处理下一个物品);先遍历容量再遍历物品,是逐列填充(每一列对应一个固定容量,先确定所有物品在该容量下的最优解)。实际解题中更常用「先遍历物品,再遍历容量」的顺序,符合我们「逐个考虑物品是否放入」的思考逻辑。

2.5 步骤5:打印dp数组(验证)

这一步是直接验证表格填充结果的正确性------通过逐步填充表格、打印中间状态,确认每一步都符合递推规则,避免因规则理解偏差导致填充错误。以示例weights = [2,3,4,5]values = [3,4,5,6]capacity = 8为例,逐步填充核心表格验证逻辑:

以示例weights = [2,3,4,5]values = [3,4,5,6]capacity = 8为例,逐步填充表格验证逻辑:

初始DP数组(第一行、第一列为0):

前i个物品\背包容量j 0 1 2 3 4 5 6 7 8
0(无物品) 0 0 0 0 0 0 0 0 0

步骤1:处理第1个物品(重量2,价值3)

j≥2时取max(上一行j, 上一行j-2+3):

前i个物品\背包容量j 0 1 2 3 4 5 6 7 8
1(物品[2,3]) 0 0 3 3 3 3 3 3 3

步骤2:处理第2个物品(重量3,价值4)

j≥3时取max(上一行j, 上一行j-3+4):

前i个物品\背包容量j 0 1 2 3 4 5 6 7 8
2(物品[2,3]、[3,4]) 0 0 3 4 4 7 7 7 7

步骤3:处理第3个物品(重量4,价值5)

j≥4时取max(上一行j, 上一行j-4+5):

前i个物品\背包容量j 0 1 2 3 4 5 6 7 8
3(新增[4,5]) 0 0 3 4 5 7 8 9 9

步骤4:处理第4个物品(重量5,价值6)

j≥5时取max(上一行j, 上一行j-5+6):

前i个物品\背包容量j 0 1 2 3 4 5 6 7 8
4(新增[5,6]) 0 0 3 4 5 7 8 9 10

最终dp[4][8] = 10,与测试用例结果一致,即最大价值为10。

完整二维DP数组变化汇总表:

前i个物品\背包容量j 0 1 2 3 4 5 6 7 8
0(无物品) 0 0 0 0 0 0 0 0 0
1(物品[2,3]) 0 0 3 3 3 3 3 3 3
2(物品[2,3]、[3,4]) 0 0 3 4 4 7 7 7 7
3(新增[4,5]) 0 0 3 4 5 7 8 9 9
4(新增[5,6]) 0 0 3 4 5 7 8 9 10
  • 时间复杂度:O(n*C)n为物品数,C为背包容量);

  • 空间复杂度:O(n*C)(二维数组的空间开销)。

相关推荐
@小码农10 小时前
6547网:202512 GESP认证 C++编程 一级真题题库(附答案)
java·c++·算法
持续升级打怪中10 小时前
ES6 Promise 完全指南:从入门到精通
前端·javascript·es6
自然语10 小时前
人工智能之数字生命-特征类升级20260106
人工智能·算法
AC赳赳老秦10 小时前
前端可视化组件开发:DeepSeek辅助Vue/React图表组件编写实战
前端·vue.js·人工智能·react.js·信息可视化·数据分析·deepseek
菜鸟233号10 小时前
力扣343 整数拆分 java实现
java·数据结构·算法·leetcode
小白冲鸭11 小时前
苍穹外卖-前端环境搭建-nginx双击后网页打不开
运维·前端·nginx
赫凯11 小时前
【强化学习】第五章 时序差分算法
算法
wulijuan88866611 小时前
Web Worker
前端·javascript
深念Y11 小时前
仿B站项目 前端 3 首页 整体结构
前端·ai·vue·agent·bilibili·首页
IT_陈寒11 小时前
React 18实战:这5个新特性让我的开发效率提升了40%
前端·人工智能·后端