474. 一和零

这不过这个背包有两个维度,一个是m 一个是n,而不同长度的字符串就是不同大小的待装物品。
开始动规五部曲:

1. 确定dp数组(dp table)以及下标的含义

dp[i][j]:最多有i个0和j个1的strs的最大子集的长度为dp[i][j]

  1. 确定递推公式

dp[i][j] 可以由前一个strs里的字符串推导出来,strs里的字符串有zeroNum个0,oneNum个1。
dp[i][j] 就可以是 dp[i - zeroNum][j - oneNum] + 1。
然后我们在遍历的过程中,取dp[i][j]的最大值。
所以递推公式:dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
此时大家可以回想一下01背包的递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
对比一下就会发现,字符串的zeroNum和oneNum相当于物品的重量(weight[i]),字符串本身的个数相当于物品的价值(value[i])。
这就是一个典型的01背包! 只不过物品的重量有了两个维度而已。

2. dp数组如何初始化

因为物品价值不会是负数,初始为0,保证递推的时候dp[i][j]不会被初始值覆盖。

3. 确定遍历顺序

那么本题也是,物品就是strs里的字符串,背包容量就是题目描述中的m和n。
代码如下:

  1. for (string str : strs) { // 遍历物品
  2. int oneNum = 0, zeroNum = 0;
  3. for (char c : str) {
  4. if (c == '0') zeroNum++;
  5. else oneNum++;
  6. }
  7. for (int i = m; i >= zeroNum; i--) { // 遍历背包容量且从后向前遍历!
  8. for (int j = n; j >= oneNum; j--) {
  9. dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
  10. }
  11. }
  12. }

4. 举例推导dp数组

以输入:[“10”,”0001”,”111001”,”1”,”0”],m = 3,n = 3为例
最后dp数组的状态如下所示:
image.png

  1. class Solution {
  2. public:
  3. int findMaxForm(vector<string>& strs, int m, int n) {
  4. vector<vector<int>> dp(m+1,vector<int>(n+1));
  5. for(string str:strs)
  6. {
  7. int zero = 0,one = 0;
  8. for(char c:str)
  9. {
  10. if(c == '0')zero++;
  11. else one++;
  12. }
  13. for(int i=m;i>=zero;i--)
  14. for(int j=n;j>=one;j--)
  15. dp[i][j] = max(dp[i][j] , dp[i-zero][j-one]+1);
  16. }
  17. return dp[m][n];
  18. }
  19. };