用填充表格法吃透01背包及其变形-1

用填充表格法吃透01背包及其变形

01背包问题是动态规划(Dynamic Programming, DP)领域最经典、最基础的模型之一,后续很多复杂的DP问题都可看作是它的变形或延伸。本文将从"表格可视化"核心思路出发,先通过空表格建立解题框架,再用DP解题5步"万能钥匙"逐步填充表格,最终覆盖基础01背包解法、空间优化技巧,以及4类经典变形的完整拆解(含代码实现),帮你彻底吃透01背包问题。

核心前置认知:01背包的本质是"选或不选"的二选一决策------有n个物品,每个物品有重量和价值,背包有固定容量,要求选择若干物品放入背包,使得总重量不超过容量的前提下,总价值最大(基础模型)。后续所有变形都围绕"选或不选"的核心逻辑展开,只是"物品""容量""目标"的具体含义不同。

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

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][j]代表什么),这是填充表格的基础;

  2. 确定递推公式 :明确单元格dp[i][j]的数值如何通过其他已填充单元格推导得出(即"选或不选"的决策逻辑),这是表格填充的核心规则;

  3. dp数组如何初始化:确定表格的初始状态(如无物品、容量为0时的单元格值),这是表格填充的起点;

  4. 确定遍历顺序(表格填充顺序):明确按什么顺序逐个填写表格中的单元格(如先逐行填、再逐列填),确保推导时依赖的单元格已提前填充;

  5. 打印dp数组(验证):通过逐步填充表格、打印中间结果,验证填充规则的正确性,避免逻辑偏差。

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

一、基础01背包(二维DP解法)

我们先从最直观的二维DP解法入手,严格按照5步「万能钥匙」拆解,完整演示基础表格的填充过程。

示例:有4个物品,重量数组weights = [2,3,4,5],价值数组values = [3,4,5,6],背包最大容量capacity = 8,求能放入背包的最大价值。

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

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

对应表格维度:i(行)表示物品数量(从0到4,0代表无物品),j(列)表示背包容量(从0到8,0代表容量为0),表格共5行9列(i:0-4,j:0-8)。

1.2 步骤2:确定递推公式

对于第i个物品(重量weights[i-1]、价值values[i-1],注意数组索引从0开始,i从1开始),有两种核心决策:选或不选。

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

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

最终递推公式(取两种决策的最大值):

javascript 复制代码
if (j < weights[i - 1]) {
  // 容量不足,无法选第i个物品
  dp[i][j] = dp[i - 1][j];
} else {
  // 容量充足,选或不选取最大值
  dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weights[i - 1]] + values[i - 1]);
}

这就是表格中每个单元格的填充规则------每个单元格的值要么继承上一行同列的值,要么继承上一行左侧对应容量的的值并加上当前物品价值,取两者最大。

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

初始化的核心是确定表格的"边界条件",即无需推导就能直接确定的单元格值。

  1. i=0(无物品) :无论背包容量j多大,放入0个物品的最大价值都是0,因此dp[0][j] = 0(表格第0行全为0);

  2. j=0(容量为0) :无论有多少物品,都无法放入背包,最大价值都是0,因此dp[i][0] = 0(表格第0列全为0)。

初始化后的表格(第0行、第0列已填充):

前i个物品\背包容量j 0 1 2 3 4 5 6 7 8
0(无物品) 0 0 0 0 0 0 0 0 0
1(物品1:w=2,v=3) 0 待填 待填 待填 待填 待填 待填 待填 待填
2(物品2:w=3,v=4) 0 待填 待填 待填 待填 待填 待填 待填 待填
3(物品3:w=4,v=5) 0 待填 待填 待填 待填 待填 待填 待填 待填
4(物品4:w=5,v=6) 0 待填 待填 待填 待填 待填 待填 待填 待填

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

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

  1. 先遍历物品(i从1到n),再遍历容量(j从1到C):逐行填充表格,先填完第1个物品对应的所有容量(第1行),再填第2个物品对应的所有容量(第2行),直到填完所有物品;

  2. 先遍历容量(j从1到C),再遍历物品(i从1到n):逐列填充表格,先填完容量1对应的所有物品数量(第1列),再填容量2对应的所有物品数量(第2列),直到填完所有容量。

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

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

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

  1. 填充第1行(i=1,物品1:w=2,v=3)

    填充后第1行:[0,0,3,3,3,3,3,3,3]

    • j=1:容量<2,无法选,dp[1][1] = dp[0][1] = 0;

    • j=2:容量≥2,选则价值=dp[0][0]+3=3,不选则0,取max=3;

    • j=3-8:选则价值=dp[0][j-2]+3=3,不选则0,取max=3;

  2. 填充第2行(i=2,物品2:w=3,v=4)

    填充后第2行:[0,0,3,4,4,7,7,7,7]

    • j=1-2:容量<3,dp[2][j] = dp[1][j](0,3);

    • j=3:选则dp[1][0]+4=4,不选则3,取max=4;

    • j=4:选则dp[1][1]+4=4,不选则3,取max=4;

    • j=5:选则dp[1][2]+4=3+4=7,不选则3,取max=7;

    • j=6-8:选则dp[1][j-3]+4=3+4=7,不选则3,取max=7;

  3. 填充第3行(i=3,物品3:w=4,v=5)

    填充后第3行:[0,0,3,4,5,5,8,9,9]

    • j=1-3:容量<4,dp[3][j] = dp[2][j](0,0,3);

    • j=4:选则dp[2][0]+5=5,不选则4,取max=5;

    • j=5:选则dp[2][1]+5=5,不选则4,取max=5;

    • j=6:选则dp[2][2]+5=3+5=8,不选则7,取max=8;

    • j=7:选则dp[2][3]+5=4+5=9,不选则7,取max=9;

    • j=8:选则dp[2][4]+5=4+5=9,不选则7,取max=9;

  4. 填充第4行(i=4,物品4:w=5,v=6)

    填充后第4行:[0,0,3,4,5,6,8,9,10]

    • j=1-4:容量<5,dp[4][j] = dp[3][j](0,0,3,4);

    • j=5:选则dp[3][0]+6=6,不选则5,取max=6;

    • j=6:选则dp[3][1]+6=6,不选则8,取max=8;

    • j=7:选则dp[3][2]+6=3+6=9,不选则9,取max=9;

    • j=8:选则dp[3][3]+6=4+6=10,不选则9,取max=10;

最终填充完成的表格:

前i个物品\背包容量j 0 1 2 3 4 5 6 7 8
0(无物品) 0 0 0 0 0 0 0 0 0
1(物品1:w=2,v=3) 0 0 3 3 3 3 3 3 3
2(物品2:w=3,v=4) 0 0 3 4 4 7 7 7 7
3(物品3:w=4,v=5) 0 0 3 4 5 5 8 9 9
4(物品4:w=5,v=6) 0 0 3 4 5 6 8 9 10

表格右下角dp[4][8] = 10,即该示例的最大价值为10,与预期结果一致。

1.6 基础二维DP完整代码(JavaScript)

javascript 复制代码
/**
 * 基础01背包(二维DP解法)
 * @param {number[]} weights - 物品重量数组
 * @param {number[]} values - 物品价值数组
 * @param {number} capacity - 背包最大容量
 * @returns {number} - 背包能容纳的最大价值
 */
function knapsack_2d(weights, values, capacity) {
  const n = weights.length;
  // 1. 初始化二维dp数组:dp[i][j]表示前i个物品放入容量j的背包的最大价值
  const dp = new Array(n + 1).fill(0).map(() => new Array(capacity + 1).fill(0));

  // 2. 遍历顺序:先遍历物品(i从1到n),再遍历容量(j从1到capacity)(逐行填充)
  for (let i = 1; i <= n; i++) {
    for (let j = 1; j <= capacity; j++) {
      // 3. 递推公式:容量不足则不选,容量充足则选或不选取最大值
      if (j < weights[i - 1]) {
        dp[i][j] = dp[i - 1][j];
      } else {
        dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weights[i - 1]] + values[i - 1]);
      }
    }
  }

  // 打印完整dp数组(表格)验证
  console.log('基础01背包二维DP数组(表格):');
  for (let i = 0; i <= n; i++) {
    console.log(dp[i].join('\t'));
  }

  // 最终答案:前n个物品放入容量capacity的背包的最大价值
  return dp[n][capacity];
}

// 测试用例
const weights = [2, 3, 4, 5];
const values = [3, 4, 5, 6];
const capacity = 8;
console.log('最大价值:', knapsack_2d(weights, values, capacity)); // 输出:10
相关推荐
持续升级打怪中11 小时前
ES6 Promise 完全指南:从入门到精通
前端·javascript·es6
自然语11 小时前
人工智能之数字生命-特征类升级20260106
人工智能·算法
AC赳赳老秦11 小时前
前端可视化组件开发:DeepSeek辅助Vue/React图表组件编写实战
前端·vue.js·人工智能·react.js·信息可视化·数据分析·deepseek
菜鸟233号11 小时前
力扣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%
前端·人工智能·后端
leiming611 小时前
c++ find_if 算法
开发语言·c++·算法