『力扣题解』为什么01背包中,使用滚动数组时容量的遍历顺序从后往前

338 阅读2分钟

接触到01背包问题后,大家一开始都是学习使用二维数组,然后各种算法博客会告诉你“可以压缩空间,使用滚动数组”。但是很多文章感觉都没给我解释懂,刚才我去看代码随想录,里面说“倒序遍历是为了保证物品i只被放入一次”,为什么倒序就可以只放入一次呢?

首先,在二维的情况下,dp[i][j]表示选取前i个物品,背包容量为j的时候,最大的价值。递推公式是dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i])。可以看出,当前行(i)状态只依赖于上一行(i - 1),在此处借用宫水三叶的一张图:

image.png

滚动数组只保存一行。假设遍历时,依然外层遍历物品,内层遍历容量。

外层循环遍历一轮(假如此时i = 3),这一轮结束时,滚动数组保存的就是只考虑前三个物品,容量为0~n时的最大价值。

然后在下一轮循环(i = 4)刚开始时,dp数组就是上一轮计算的结果,不用像二维dp数组那样去拿i - 1行的数,因为dp目前就保存了i - 1行的结果,直接拿来用就好。

假设黑色表示上一轮(i = 3)的结果,红色表示这一轮(i = 4)更新过的结果:

image.png

也就是说,如果从左往右遍历,红色格子可能会从红色格子得到,在逻辑上表现为同一个物品拿了多次。而从右往左遍历,红色格子只可能从黑色格子得来,物品只拿一次。

如果再次考虑dp是二维的,那么从前往后和从后往前的转移方程可以等价为:

  • 从左往右:dp[i][j] = Math.max(dp[i][j], dp[i][j - w[i]] + v[i])
  • 从右往左:dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i])

最后,滚动数组的转移方程是: dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]),第一个不是dp[j - 1],刚刚做题差点写错...