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

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

初始化逻辑与二维一致:容量为0时,最大价值为0,因此dp[0] = 0;其他容量的初始值也为0(因为初始无物品可放,最大价值为0),即dp = new Array(capacity + 1).fill(0)

初始化后的单行表格:[0,0,0,0,0,0,0,0,0](j从0到8)

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

一维DP的遍历顺序有严格要求,核心是「倒序遍历容量」,对应单行表格的「从右往左填充」------明确单行表格的填充顺序是避免重复选择物品的关键:

  1. 必须先遍历物品,再遍历容量:逐个处理每个物品,每次处理时更新整个单行表格(覆盖上一行结果);

  2. 容量必须倒序遍历(j从C到weights[i-1]) :从最大容量往小容量填充,确保计算dp[j]时,dp[j - weights[i-1]]仍是上一行(未处理当前物品)的旧值,避免同一物品被多次选择。

关键原因:一维DP的核心是用单行表格复用二维表格的空间,表格中每个位置的数值都依赖"上一轮未更新的旧值"(对应二维的dp[i-1][j - w[i]])。若正序遍历容量,dp[j - w[i]]会被提前更新(相当于二维的dp[i][j - w[i]]),导致同一个物品被多次选择(变成完全背包);倒序遍历能保证计算dp[j]时,dp[j - w[i]]仍是上一行(未选当前物品)的结果,对应单行表格从右往左填充,完美契合01背包「每个物品选一次」的规则。

反例(正序遍历容量):若j从weights[i-1]到C正序遍历,处理物品1(w=2,v=3)时,j=2会更新dp[2]=3,j=4时会用到dp[2]的新值(3),计算dp[4] = dp[4] + 3 = 3,相当于把物品1放入了两次,违背01背包规则。

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

通过打印单行表格的滚动更新过程,验证填充规则的正确性。仍用测试用例 weights = [2,3,4,5]values = [3,4,5,6]capacity = 8,演示一维DP数组(单行表格)的填充变化:

  1. 初始状态:dp = [0,0,0,0,0,0,0,0,0]

  2. 处理物品1(w=2,v=3),j从8到2倒序

    更新后:dp = [0,0,3,3,3,3,3,3,3]

    • j=8:dp[8] = max(0, dp[8-2]+3) = max(0,0+3)=3;

    • j=7:dp[7] = max(0, dp[5]+3)=3;

    • ...(j=2到6同理);

    • j=2:dp[2] = max(0, dp[0]+3)=3;

  3. 处理物品2(w=3,v=4),j从8到3倒序

    更新后:dp = [0,0,3,4,4,7,7,7,7]

    • j=8:max(3, dp[5]+4)=max(3,3+4)=7;

    • j=7:max(3, dp[4]+4)=max(3,3+4)=7;

    • j=6:max(3, dp[3]+4)=max(3,3+4)=7;

    • j=5:max(3, dp[2]+4)=max(3,3+4)=7;

    • j=4:max(3, dp[1]+4)=max(3,0+4)=4;

    • j=3:max(3, dp[0]+4)=max(3,0+4)=4;

  4. 处理物品3(w=4,v=5),j从8到4倒序

    更新后:dp = [0,0,3,4,5,5,8,9,9]

    • j=8:max(7, dp[4]+5)=max(7,4+5)=9;

    • j=7:max(7, dp[3]+5)=max(7,4+5)=9;

    • j=6:max(7, dp[2]+5)=max(7,3+5)=8;

    • j=5:max(7, dp[1]+5)=max(7,0+5)=7;

    • j=4:max(4, dp[0]+5)=max(4,0+5)=5;

  5. 处理物品4(w=5,v=6),j从8到5倒序

    更新后:dp = [0,0,3,4,5,6,8,9,10]

    • j=8:max(9, dp[3]+6)=max(9,4+6)=10;

    • j=7:max(9, dp[2]+6)=max(9,3+6)=9;

    • j=6:max(8, dp[1]+6)=max(8,0+6)=8;

    • j=5:max(5, dp[0]+6)=max(5,0+6)=6;

最终单行表格dp[8] = 10,与二维DP结果一致,验证了优化的正确性。

2.6 一维DP空间优化完整代码(JavaScript)

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

  // 2. 遍历顺序:先遍历物品(i从0到n-1),再倒序遍历容量(j从capacity到weights[i])(从右往左填充)
  for (let i = 0; i < n; i++) {
    // 倒序遍历避免重复选择当前物品
    for (let j = capacity; j >= weights[i]; j--) {
      // 3. 递推公式:不选当前物品的最大价值 vs 选当前物品的最大价值
      dp[j] = Math.max(dp[j], dp[j - weights[i]] + values[i]);
    }
    // 打印每次处理物品后的dp数组(单行表格更新过程)
    console.log(`处理完物品${i + 1}后,dp数组:`, [...dp]);
  }

  // 最终答案:容量为capacity的背包的最大价值
  return dp[capacity];
}

// 测试用例
const weights1 = [2, 3, 4, 5];
const values1 = [3, 4, 5, 6];
const capacity1 = 8;
console.log('最大价值:', knapsack_1d(weights1, values1, capacity1)); // 输出:10
相关推荐
南囝coding8 小时前
发现一个宝藏图片对比工具!速度比 ImageMagick 快 6 倍,还是开源的
前端
漫随流水8 小时前
leetcode算法(145.二叉树的后序遍历)
数据结构·算法·leetcode·二叉树
Tony_yitao9 小时前
22.华为OD机试真题:数组拼接(Java实现,100分通关)
java·算法·华为od·algorithm
前端小黑屋9 小时前
查看 Base64 编码的字体包对应的字符集
前端·css·字体
JavaGuru_LiuYu9 小时前
Spring Boot 整合 SSE(Server-Sent Events)
java·spring boot·后端·sse
2501_941875289 小时前
在东京复杂分布式系统中构建统一可观测性平台的工程设计实践与演进经验总结
c++·算法·github
xuejianxinokok9 小时前
如何在 Rust 中以惯用方式使用全局变量
后端·rust
爬山算法9 小时前
Hibernate(26)什么是Hibernate的透明持久化?
java·后端·hibernate
sonadorje9 小时前
梯度下降法的迭代步骤
算法·机器学习
彭于晏Yan9 小时前
Springboot实现数据脱敏
java·spring boot·后端