• 背包问题总结


    转自: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. 组合总和 Ⅳ,完全背包问题,求组合数,结果可以重复,所以是对物品的排列,需要先遍历容量,再遍历物品。

    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. 一和零

  • 相关阅读:
    力扣(LeetCode)验证回文字符串II 个人题解
    力扣(LeetCode)寻找数组的中心索引 个人题解
    力扣(LeetCode)验证回文串 个人题解
    力扣(LeetCode)三个数的最大乘积 个人题解
    力扣(LeetCode)二进制求和 个人题解
    力扣(LeetCode)加一 个人题解
    力扣(LeetCode)整数反转 个人题解
    力扣(LeetCode)颠倒二进制位 个人题解
    力扣(LeetCode)最后一个单词的长度 个人题解
    力扣(LeetCode)学生出勤记录I 个人题解
  • 原文地址:https://www.cnblogs.com/BlueBlueSea/p/14425638.html
Copyright © 2020-2023  润新知