背包问题总结

转自:https://blog.csdn.net/yandaoqiusheng/article/details/84782655/,背包九讲

1.0-1背包问题

有背包容量为V,N个物品,每个重量为w[i],价值为v[i],每个物品仅有一件,要使得背包价值最大怎么装物品?

定义dp数组含义:dp[i][j]表示考虑前i个物品在背包容量恰为j的情况下达到的最大价值。

状态转移方程:

f[i][j]=max(f[i−1][j],f[i−1][j−w[i]]+v[i])

解释:针对第i个物品有两种选择,一是放进背包,一是不放进背包。max中的第一项就是不放进背包,那么就转换为考虑前i-1件物品放进容量为j的背包;第二项是放进背包,问题转化为前i-1件物品放入容量为j-w[i]的背包,此时的最大价值是再加上当前的物品的价值。

1.1 空间优化

使用滚动数组的思想:

for (int i = 1; i <= n; i++)//遍历物品
    for (int j = V; j >= 0; j--)//遍历容量
        f[j] = max(f[j], f[j - w[i]] + v[i]);

外层遍历物品,内层要从大到小遍历,因为物品最多只能取一次。(完全背包问题中是从小到大遍历容量,因为可以取无限次。)

只要数量有限制都只能从大到小遍历,其实从原始公式中看的更明显,从大到小只保证使用的是i-1的计算结果。

总结:如果容量从小到大遍历,那么就会重复计算物品,使用的是之前计算的结果,针对完全背包问题;那么针对01背包问题,就需要从后往前,就不会重复计算包了。

力扣例题:416. 分割等和子集

2.完全背包问题

有背包容量为V,N个物品,每个重量为w[i],价值为v[i],每个物品有无数件,要使得背包价值最大怎么装物品?

dp数组含义和上面相同,按照每个物品可取数量可以这么写,

f[i][j]=max(f[i−1][j−k∗w[i]]+k∗v[i])∣0<=k∗w[i]<=j

但是不好解。

2.1 转换为0-1问题

for (int i = 1; i <= n; i++)//遍历物品
    for (int j = w[i]; j <= V; j++)//遍历容量
        f[j] = max(f[j], f[j - w[i]] + v[i]);

遍历物品,但背包容量是从小到大的,因为物品可以无限次使用。

两层for循环可以替换?

举例:322. 零钱兑换

        for(int i=1;i<=amount;i++){//容量在外,每次开始一次都表示之前的容量已确定最终的结果
            for(int j=0;j<coins.size();j++){
                if(coins[j]>i)continue;
                dp[i]=min(dp[i],dp[i-coins[j]]+1);
            }
        }
        for(int i=0;i<coins.size();i++){//硬币在外,每次都用一个硬币更新所有的容量一次
            for(int j=coins[i];j<=amount;j++){
                dp[j]=min(dp[j],dp[j-coins[i]]+1);
            }
        }

 377. 组合总和 Ⅳ,完全背包问题,求组合数,结果可以重复,所以是对物品的排列,需要先遍历容量,再遍历物品。

总结:

* 1.0-1背包问题,外层遍历物品,内层从大到小遍历容量;(因为只能用一次)
* 完全背包问题,外层便利物品,内层从小到大遍历容量。(能用多次)

70. 爬楼梯,如果本题用背包思想来做,因为是排列问题,不是组合问题,所以需要遍历容量,如果遍历物品的话,那么物品的顺序是定的,就出现方法数变少的错误。

!!!错误的代码!!!
class Solution {
public:
    int climbStairs(int n) {
        //使用完全背包思想来求解
        vector<int> stairs={1,2};
        vector<int> dp(n+1,0);
        dp[0]=1;
        for(int i=0;i<stairs.size();i++){
            for(int j=0;j<=n;j++){
                if(stairs[i]>j)continue;
                dp[j]+=dp[j-stairs[i]];
            }
        }
        return dp[n];
    }
};
//正确的代码:
class Solution3 {
public:
    int climbStairs(int n) {
        //使用完全背包思想来求解
        vector<int> stairs={1,2};
        vector<int> dp(n+1,0);
        dp[0]=1;
        for(int i=1;i<=n;i++){//排列问题,遍历每个容量,考虑物品顺序
            for(int j=0;j<stairs.size();j++){
                if(stairs[j]>i)continue;
                dp[i]+=dp[i-stairs[j]];
            }
        }
        return dp[n];
    }
};

2.2 初始化问题

  • 要求恰好装满容量V:那么dp[0]=0,其他都初始化为负无穷,表示只有容量为0的背包可能被价值为0的nothing“恰好装满”,其它容量的背包均没有合法的解,属于未定义的状态。
  • 不求装满,只求最优:那么所有的数都初始化为0,表示任何容量的背包都有一个合法解“什么都不装”,这个解的价值为0。

3.多重背包问题

每件物品多了个数量限制。

//还没有做过这样得的题目。

 

4.混合背包问题

有的可以取0-1次,有的可以取无限次,有的能取k次。

那么针对0-1和完全可以分开求解:

p[i]:每个物品的件数,0代表无穷个
for (int i = 1; i <= n; i++)//遍历物品
    if (p[i] == 0)//完全背包问题
        for (int j = w[i]; j <= V; j++)//从小到大遍历
            f[j] = max(f[j], f[j - w[i]] + v[i]);
    else
    for (int k = 1; k <= p[i]; k++)//如果p[i]是1,就是0-1背包问题,否则是多重背包问题
        for (int j = V; j >= w[i]; j--)
            f[j] = max(f[j], f[j - w[i]] + v[i]);

//还没有做过这样的题目,希望做的时候能想到解法。

5.二维费用背包问题

现在物品有两种重量,两种价值,当然也有两个背包,也就是有两个限制,那么就再多加一维状态:

定义dp数组含义:dp[i][j][k]表示考虑前i件物品,背包容量分别为j和k时能达到的最大价值。

状态转移:

max(f[i−1][j][k],f[i−1][j−w[i]][k−g[i]]+v[i])//不放、放

5.1 空间优化

那么此时就需要从大到小遍历:

for (int i = 1; i <= n; i++)
    for (int j = V; j >= w[i]; j--)//两层均需要从大到小遍历
        for (int k = T; k >= g[i]; k--)
            f[j][k] = max(f[j][k], f[j - w[i]][k - g[i]] + v[i]);

时间复杂度就是三层for循环乘积了,空间复杂度降成了二维。

题目:474. 一和零

 

posted @ 2021-02-21 13:55  lypbendlf  阅读(114)  评论(0编辑  收藏  举报