leetcode 474

题目:

难度中等163

在计算机界中,我们总是追求用有限的资源获取最大的收益。

现在,假设你分别支配着 m 个 0 和 n 个 1。另外,还有一个仅包含 0 和 1 字符串的数组。

你的任务是使用给定的 m 个 0 和 n 个 1 ,找到能拼出存在于数组中的字符串的最大数量。每个 0 和 1 至多被使用一次

注意:

  1. 给定 0 和 1 的数量都不会超过 100
  2. 给定字符串数组的长度不会超过 600

示例 1:

输入: Array = {"10", "0001", "111001", "1", "0"}, m = 5, n = 3
输出: 4
解释: 总共 4 个字符串可以通过 5 个 0 和 3 个 1 拼出,即 "10","0001","1","0" 。

示例 2:

输入: Array = {"10", "0", "1"}, m = 1, n = 1
输出: 2
解释: 你可以拼出 "10",但之后就没有剩余数字了。更好的选择是拼出 "0" 和 "1" 。

题解:

     这道题很明显是一个二重背包问题,我们将字符串设为放进背包的物体,0和1的数量可以视为物体的体积和重量(限制条件),字符串的数量就是我们所求的物体总价值。

     现在,我们设当前可以选择放进背包的字符串有i种,背包目前允许的最大体积和最大重量是j,k(目前能用的0和1的总数),我们可以写出状态转移方程:

dp[i][j][k] = dp[i-1][j][k]  (第i种放不下了,所以结果和只允许放前i-1种物品时一样)

dp[i][j][k] = max(dp[i-1][j-zero][k-one]+1)(第i种可以放下,因此它相当于只允许放前i-1种物品时的情况加1,j-zero和k-one是因为之前这点体积根本就没有用,max是因为我们要取最大值,万一放了结果更不好呢?)

      先放O(n3)的代码:

      

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

          相信大家也会觉得,这个时间复杂度有点太高了,那么,该怎么解决呢?

  题解二:

          我们发现,dp[i][j][k]的值只和i-1时的有关,我们设共有strs.size()张表,表的x轴和y轴就可以设为j和k,即第i-1张表和第i张表是不同时间维度的表。我们用二维数组来表示,dp[j][k]可以暂时储存它在i-1时刻的数值,然后在i时刻再改变,再看一眼公式:

          也就是说dp[j][k]的值只和上一张表的dp[j-zero][k-zero]有关,它自己本身其实还储存着dp[j][k](第i-1张表)的旧值。如果我们从0到m,0到n会发生什么事情呢?首先被更新的应该是第i张表的dp[j-zero][k-zero],它的值变成了第i张表的新值,轮到更新dp[j][k]时,它需要的是第i-1张表的dp[j-zero][k-zero](旧值),但是dp[j-zero][k-zero]已经在它之前更新成为i时刻的新值了。

          现在我们明白顺序遍历是错误的,那为什么逆序从m到0,从n到0是正确的呢?

          因为dp[j][k]的值只和上一张表有关,换言之,它需要的值都已经算出来了(都是旧值),而逆序遍历之后,如dp[m][n]被更新后,dp[m-x][n-y]根本就不需要用到dp[m][n]的旧值,因此不会影响后面的更新。

          

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

         为什么是从m到zero,而不是0呢?因为之前从m到0是为了初始化dp[i][j][k] = dp[i-1][j][k],最糟糕的情况就是可以选择的物品多了,但是还和之前的情况一样没有变动。但是在二维数组情况里,dp[j][k]本身就储存了i-1时刻的旧值了,最糟糕也就是这种情况了,在j<zero || k<one时刻,根本没有办法改变,所以是m到zero遍历。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值