上一次我们分享了动态规划-背包问题的解题方法,最后我们提出了一个疑问,就是如何降低我们的空间复杂度呢?
用滚动数组可以大大减少使用空间。它能把二维状态方程O(n^2)的空间复杂度优化到一维的O(n),更高维的数组也可以优化后减少一维。
从上节介绍过的dp转移方程我们不难发现,dp[i][] 只与dp[i-1][] 有关,和前面无关,那些用过的已经多余了,那么干脆就服用这些空间,用新的一行覆盖已经无用的一行(滚动),只需要两行就够了。
滚动数组分为两种:交替滚动和自我滚动
1. 交替滚动
我们用now始终指向正在计算的最新一行,old指向已经算过的旧的一行,对照原递推代码,now相当于i,old相当于i-1。
const int N = 1005;
int dp[N][N],w[N],c[N];
int solve(int n,int C){
int now=0,old=1;
for(int i=1;i<=n;i++){
swap(old,now);
for(int j=1;j<=C;j++){
if(c[i]>j) dp[now]=dp[old][j];
else dp[now][j]=max(dp[old][j],dp[old][j-c[i]]+w[i]);
}
}
return dp[now][C];
}
这里需要我们注意的是,j循环反过来(C~0)也是可以的,但是在下面的自我滚动中,j循环必须是C~0。
2.自我滚动
const int N = 1005;
int dp[N], w[N], c[N];
int solve(int n, int C) {
for(int i=1;i<=n;i++){
for(int j=C;j>=c[i];j--){
dp[j]=max(dp[j],dp[j-c[i]]+w[i]);
}
}
return dp[C];
}
可以思考以下问什么自我滚动的j循环就必须从C~0?
答:错误的产生是动数组重复使用同一个空间引起的。什么意思呢?加入我们现在要将物品放到我们的dp[5]的这个背包中(假设这个物品放进去合适),那么我就就会对dp[5]进行更新,就设为dp[5]' 吧。现在我们又要将一个价值为3的物品放入我们的dp[8]的这个背包中,那么我们从动态规划方程中不难看出(dp[j]=max(dp[j],dp[j-c[i]]+w[i]);),dp需要用容积是5的背包的相关信息,那么值得我们思考的是,这里用到的信息是dp[5]' ,而不是dp[5],而我们真正需要用到的却是dp[5],这就导致了错误答案。