背包问题中遍历顺序决定选择当前物品一次or多次的详解【0-1背包 vs 完全背包】【一维dp数组】

在背包问题中,遍历容量顺序的不同之所以会影响是否重复选择同一物品,本质是因为动态规划的状态覆盖机制。虽然外层循环遍历的是物品,但内层循环的容量遍历方向会直接影响状态转移时的依赖关系。以下分步骤解释:


1. 核心逻辑:外层遍历物品,内层遍历容量

  • 0-1背包(每个物品选一次):外层遍历物品,内层逆序遍历容量。

  • 完全背包(允许重复选):外层遍历物品,内层顺序遍历容量。

关键区别

  • 逆序遍历容量时,当前物品的状态更新不会影响后续容量的计算(避免重复选择)。

  • 顺序遍历容量时,当前物品的状态更新会立即被后续容量利用(允许多次选择)。


2. 直观例子:0-1背包 vs 完全背包

假设物品重量 w=2,价值 v=5,背包容量 C=4,初始状态 dp = [0, 0, 0, 0, 0]

情况1:0-1背包(逆序遍历容量)
  • 外层循环:处理当前物品(假设是第一个物品)。

  • 内层循环:从 j=4 到 j=2(逆序)。

    • j=4dp[4] = max(dp[4], dp[4-2] + 5) = max(0, dp[2] + 5)
      此时 dp[2] 尚未被更新(仍是初始值0),所以 dp[4] = 5

    • j=2dp[2] = max(dp[2], dp[0] + 5) = 5

结果dp[4] = 5(只选了一次当前物品)。
关键点:逆序保证了计算大容量时,小容量的状态尚未被当前物品污染。

情况2:完全背包(顺序遍历容量)
  • 外层循环:处理当前物品。

  • 内层循环:从 j=2 到 j=4(顺序)。

    • j=2dp[2] = max(dp[2], dp[0] + 5) = 5

    • j=4dp[4] = max(dp[4], dp[2] + 5) = 5 + 5 = 10

结果dp[4] = 10(选了两次当前物品)。
关键点:顺序遍历时,小容量状态已被更新,大容量可以复用更新后的值。


3. 为什么容量遍历顺序会影响物品选择次数?

  • 逆序遍历容量
    在计算 dp[j] 时,dp[j - w] 的值来自上一轮外层循环(即未选当前物品时的状态)。
    → 每个物品只能在每个容量下被选一次。

  • 顺序遍历容量
    在计算 dp[j] 时,dp[j - w] 的值可能已经被当前外层循环更新过(即已选过当前物品)。
    → 允许在更大容量中重复选择当前物品。


4. 二维数组 vs 一维数组的对比

  • 二维数组(无覆盖问题):
    状态定义为 dp[i][j],表示前 i 个物品在容量 j 下的最大价值。
    无论顺序还是逆序遍历容量,都不会影响状态覆盖,因为每个物品的状态独立存储。

  • 一维数组(空间优化):
    状态压缩为 dp[j],复用同一数组。此时遍历顺序必须控制状态覆盖的时机:

    • 逆序:保证计算 dp[j] 时,dp[j - w] 是上一轮的值(未选当前物品)。

    • 顺序:允许 dp[j - w] 是当前轮的值(已选当前物品)。


总结:遍历顺序的本质

  • 遍历物品:决定“是否考虑当前物品”。

  • 遍历容量顺序:决定“如何利用已计算的状态”。

    • 逆序 → 确保每个物品只被考虑一次(0-1背包)。

    • 顺序 → 允许同一物品被多次使用(完全背包)。