问题描述

给你一个可装载重量为 W 的背包和 N 个物品,每个物品有重量价值两个属性。其中第 i 个物品的重量为 wt[i],价值为 val[i],现在让你用这个背包装物品,最多能装的价值是多少?
在这里插入图片描述

解题思路

01背包问题是一个非常经典的动态规划问题,可以很好的入门动态规划,与之相关的变形题目也是非常的多

(1)状态和选择是什么?

  • 状态:由于是不断将物品放入状态,所以变化的量有两个——“背包容量”和可选择的物品
  • 选择:选择就是要么装进去要么不装进(也就是0和1)

因此选择导致了状态变化,如果选择装入将导致背包容量减少以及可选择的物品减少

(2)dp数组如何定义

接着要根据状态和选择来定义dp数组。dp数组完成的就是动态转移,dp数组的值一定是价值,这是毋庸置疑的。动态规划框架如下
在这里插入图片描述
题目问的是当给定物品个数为N,背包容量为W时所能装的最大的价值数目,那么我们将dp数组也定义为dp[i][w](动态规划本质就在于状态的转移,就像是砌墙一样,上面的每一砖每一瓦都代表一个状态,它们的到的和下面的砖瓦是息息相关的),它表示对于前i个物品,如果给定容量为w,可以装的最大价值为dp[i][w]

后序的操作就是从最简单的情形开始,一步一步推导每个状态,最终当返回i=Nw=W时的结果即可。

(3)base case

base case很容易想到。最极端最简单的情况自然是i=0,此时表示不选择物品,以及w=0,此时表示背包容量为0。他们的结果均为0

(4)框架

于是完善最原始的框架如下

int dp[N+1][W+1]
dp[0][..] = 0
dp[..][0] = 0

for i in [1..N]:
    for w in [1..W]:
        dp[i][w] = max(
            把物品 i 装进背包,
            不把物品 i 装进背包
        )
return dp[N][W]

动态规划最难的地方,就是动态转移,也即是如何将上面的文字表述为代码

(5)动态转移

  • 如果没有把第i个物品放在背包:很显然,既然没有把第i个放进去,那么价值量不会增加,状态也不会变化,也即dp[i][w]==dp[i-1][w]
  • 如果把第i个物品放入了背包:既然放入了背包,那么此状态的容量一定会减少wt[i],而价值则会增加val[i],因此dp[i][w]==dp[i-1][w-wt[i-1]]+val[i-1]

需要注意的是i是从1开始的,因此valwt的索引中i-1表示第i个物品。所以dp[i][w]==dp[i-1][w-wt[i-1]]+val[i-1]表示如果把第i个物品装入了,就要寻找剩余重量w-wt[i-1]限制下的最大价值,加上第i个物品的价值val[i-1]

代码

int knapsack(int W, int N, vector<int>& wt, vector<int>& val) {
    // vector 全填入 0,base case 已初始化
    vector<vector<int>> dp(N + 1, vector<int>(W + 1, 0));
    for (int i = 1; i <= N; i++) {
        for (int w = 1; w <= W; w++) {
            if (w - wt[i-1] < 0) {
                // 当前背包容量装不下,只能选择不装入背包
                dp[i][w] = dp[i - 1][w];
            } else {
                // 装入或者不装入背包,择优
                dp[i][w] = max(dp[i - 1][w - wt[i-1]] + val[i-1], 
                               dp[i - 1][w]);
            }
        }
    }

    return dp[N][W];
}

01背包问题相关题目

1:牛客-求正数数组的最小不可组成和

牛客
在这里插入图片描述

如果按照原生的背包问题可以这样理解:min为最轻物品的质量,sum为所有物品的总质量,假设有一个背包,其容量范围在[min,sum]之间,还有len件不同重量的物品、、、

也即把数组中的数据看作物品的重量,如果这些物品不能填满某个容量(范围为[min,max])的背包,就表示不能组成那个范围的数

class Solution {
public:
    /**
     *    正数数组中的最小不可组成和
     *    输入:正数数组arr
     *    返回:正数数组中的最小不可组成和
     */
    int getFirstUnFormedNum(vector<int> arr, int len) 
    {
        //范围为[min,sum];
        int sum=0,min=arr[0];
        int i,j;
        for(int i=0;i<len;i++)
        {
            sum+=arr[i];
            min=arr[i] < min ? arr[i] : min;
        }
        vector<int> dp(sum+1,0);
        for(i=0;i<len;i++)
        {
            for(j=sum;j>=arr[i];j--)//对于背包容量小于物品的直接忽略
            {
                if(dp[j] < dp[j-arr[i]]+arr[i])//选上了
                    dp[j]=dp[j-arr[i]]+arr[i];
                else//没选上
                    dp[j]=dp[j];
            }
        }

        //最后只要放入的重量不是那个区间的数肯定就是所求
        for(i=min;i<=sum;i++)
        {
            if(i!=dp[i])
                return i;
        }
        return sum+1;
    }
};