动态规划学习(模板使用方法)

例题

给你一个二进制字符串数组 strs 和两个整数 m 和 n 。

请你找出并返回 strs 的最大子集的大小,该子集中 最多 有 m 个 0 和 n 个 1 。

如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。

示例 1:
输入:strs = [“10”, “0001”, “111001”, “1”, “0”], m = 5, n = 3
输出:4
解释:最多有 5 个 0 和 3 个 1 的最大子集是 {“10”,“0001”,“1”,“0”} ,因此答案是 4 。
其他满足题意但较小的子集包括 {“0001”,“1”} 和 {“10”,“1”,“0”} 。{“111001”} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3 。

示例 2:
输入:strs = [“10”, “0”, “1”], m = 1, n = 1
输出:2
解释:最大的子集是 {“0”, “1”} ,所以答案是 2 。

提示:
1 <= strs.length <= 600
1 <= strs[i].length <= 100
strs[i] 仅由 ‘0’ 和 ‘1’ 组成
1 <= m, n <= 100

题解

  • 第一步,要明确两点,[状态]和[选择]。

状态有三个, [背包对1的容量]、[背包对0的容量]和 [可选择的字符串];选择就是把字符串[装进背包]或者[不装进背包]。

明白了状态和选择,只要往这个框架套就完事儿了:

for 状态1 in 状态1的所有取值:
for 状态2 in 状态2的所有取值:
for …
dp[状态1][状态2][…] = 计算(选择1,选择2…)

- 第二步,要明确dp数组的定义:

首先,[状态]有三个,所以需要一个三维的dp数组。

dp[i][j][k]的定义如下:

若只使用前i个物品,当背包容量为j个0,k个1时,能够容纳的最多字符串数。

经过以上的定义,可以得到:

base case为dp[0][…][…] = 0, dp[…][0][0] = 0。因为如果不使用任何一个字符串,则背包能装的字符串数就为0;如果背包对0,1的容量都为0,它能装的字符串数也为0。

我们最终想得到的答案就是dp[N][zeroNums][oneNums],其中N为字符串的的数量。

第三步,根据选择,思考状态转移的逻辑:

注意,这是一个0-1背包问题,每个字符串只有一个选择机会,要么选择装,要么选择不装。

如果你不能把这第 i 个物品装入背包(等同于容量不足,装不下去),也就是说你不使用strs[i]这一个字符串,那么当前的字符串数dp[i][j][k]应该等于dp[i - 1][j][k],继承之前的结果。

如果你可以把这第 i 个物品装入了背包(此时背包容量是充足的,因此要选择装或者不装),也就是说你能使用 strs[i] 这个字符串,那么 dp[i][j] 应该等于 Max(dp[i - 1][j][k], dp[i - 1][j - cnt[0]][k - cnt[1]] + 1)。 Max函数里的两个式子,分别是装和不装strs[i的字符串数量。(cnt 是根据strs[i]计算出来的。)

比如说,如果你想把一个cnt = [1,2]的字符串装进背包(在容量足够的前提下),只需要找到容量为
[j - 1][k - 2]时候的字符串数再加上1,就可以得到装入后的字符串数了。

由于我们求的是最大值,所以我们要求的是装和不装中能容纳的字符串总数更大的那一个。

代码

    int findMaxForm(vector<string>& strs, int m, int n) {
        int num=strs.size();
        int NumZero,NumOne;
        vector<vector<vector<int>>> dp(num+1, vector<vector<int>>(m+1, vector<int>(n+1,0)));
        for(int i=1;i<num+1;i++){
            NumOne=NumZero=0;
            //统计当前字符串中0和1的个数
            for(char &c:strs[i-1]){
                if(c=='0') NumZero++;
                else NumOne++;
            }
            for(int j=0;j<m+1;j++){
                for(int k=0;k<n+1;k++){
                    if(j>=NumZero && k>=NumOne)
                    dp[i][j][k]=max(dp[i-1][j][k],dp[i-1][j-NumZero][k-NumOne]+1);
                    else
                    dp[i][j][k]=dp[i-1][j][k];
                }
            }
        }
        return dp[num][m][n];
    }

ps:遍历字符串

使用 for (char c : s)时会复制一个s字符串再进行遍历操作;

而使用for (char& c : s)时直接引用原字符串进行遍历操作,由于复制一个字符串花费了大量的时间,所以第二种解法要快于第一种解法。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值