LeetCode:474. Ones and Zeroes

在这里插入图片描述
注意这题,总是不会,让我懵逼。
还是要记住自己总结的dp和背包的几点:
1.给出和题目想同但是小的条件(前i个,以及给少一点0, 1)
2.用值来做维度,也就是多少0和1来做维度。

很有意思的一道背包问题。
首先方法一:
直接背包。有点不同以往的是这需要三维的背包。

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        int sz = strs.size();
        if (sz == 0)
            return 0;
        vector<vector<vector<int>>> dp(sz+1, vector<vector<int>>(m+1, vector<int>(n+1, 0)));
        
        for (int i = 1; i < sz + 1; ++i) {
            auto cnt = cntHelper(strs[i-1]);
            int cnt0 = cnt.first;
            int cnt1 = cnt.second;
            
            for (int j = 0; j < m+1; ++j) 
                for (int k = 0; k < n+1; ++k) {
                    dp[i][j][k] = dp[i-1][j][k];
                    if (j >= cnt0 && k >= cnt1)
                        dp[i][j][k] = max(dp[i][j][k], dp[i-1][j-cnt0][k-cnt1] + 1);
                }
        }
        return dp[sz][m][n];
    }
private:
    pair<int, int> cntHelper(const string& s) {
        int m = 0;
        int n = 0;
        for (char c : s) {
            if (c == '0')
                ++m;
            else
                ++n;
        }
        return make_pair(m, n);
    }
};

方法二:
上面背包的问题在于空间需要O(sz * m * n),现在考虑降低这个空间使用。

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        int sz = strs.size();
        if (sz == 0)
            return 0;
        
        vector<vector<int>> dp(m+1, vector<int>(n+1, 0));
        for (auto& s : strs) {
            int cnt0 = 0;
            int cnt1 = 0;
            for (char c : s) {
                if (c == '0')
                    ++cnt0;
                else
                    ++cnt1;
            }
            
            for (int j = m; j >= cnt0; --j)
                for (int k = n; k >= cnt1; --k) {
                    dp[j][k] = max(dp[j][k], dp[j-cnt0][k-cnt1] + 1);
                }
        }
        
        return dp[m][n];
    }
};

这个方法的思路和之前背包方法的思路都有点区别,我尝试解析一下:
1.进入第i个string的循环里面时,这时dp里面存储的值的意义是什么?
是对于前i-1个string得到的结果。
2.对于每个string,循环里面做了什么?
做的是,对于所有对这个string有可能有提升的位置,进行提升的尝试。也就是,对于每个j >= cnt0和k >= cnt1的位置,看看是维持原样(仍然维持前i-1个string的结果,也就是不放入背包),还是将这个string放入背包
3.最让人困惑的,也就是,内部的二重循环为什么是倒着来的,也就是为什么它的方向是从右下到左上这样的迭代趋势?
如果从左上到右下,问题是:如果我们在一个位置用这个string提升了(也就是放入背包了),那么在之后,这个位置的值可能会被再次使用,也就是重复计算。
然而,当我们从右下到左上这个顺序来的时候,我们可以想象,在计算每个位置dp[j][k],它的“左上方”的所有位置(包括当前位置)的值存储的都是对于前i-1个string所求出的值,也就是:

dp[j][k] = max(dp[j][k], dp[j-cnt0][k-cnt1] + 1);

这里,它比较的是究竟是不放入背包(dp[j][k]),还是放入背包(dp[j-cnt0][k-cnt1] + 1),其中,dp[j-cnt0][k-cnt1]的值肯定没有被当前这个string更新过。然后,如果从左上到右下,则它的值可能被更新过。
那么为什么三维背包的时候没有这个问题?关键在于:

dp[i][j][k] = dp[i-1][j][k];
 if (j >= cnt0 && k >= cnt1)
 		dp[i][j][k] = max(dp[i][j][k], dp[i-1][j-cnt0][k-cnt1] + 1);

这里明确指定了只看前i-1个string的相关值,不会重复计算的。

解法来源:
https://leetcode.com/problems/ones-and-zeroes/discuss/95814/c%2B%2B-DP-solution-with-comments
https://leetcode.com/problems/ones-and-zeroes/discuss/95807/0-1-knapsack-detailed-explanation.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值