474. 一和零(力扣LeetCode)(官方代码 Java 小白细解)(知识点:动态规划)

力扣网 题目 链接
https://leetcode-cn.com/problems/ones-and-zeroes/

对应官方解答 链接
https://leetcode-cn.com/problems/ones-and-zeroes/solution/yi-he-ling-by-leetcode/

本文主要是 官方的代码 细解

题目

在这里插入图片描述在这里插入图片描述

官方代码

public class Solution {
    public int findMaxForm(String[] strs, int m, int n) {
        int[][] dp = new int[m + 1][n + 1];
        for (String s: strs) {
            int[] count = countzeroesones(s);
            for (int zeroes = m; zeroes >= count[0]; zeroes--)
                for (int ones = n; ones >= count[1]; ones--)
                    dp[zeroes][ones] = Math.max(1 + dp[zeroes - count[0]][ones - count[1]], dp[zeroes][ones]);
        }
        return dp[m][n];
    }
    public int[] countzeroesones(String s) {
        int[] c = new int[2];
        for (int i = 0; i < s.length(); i++) {
            c[s.charAt(i)-'0']++;
        }
        return c;
    }
}

作者:LeetCode
链接:https://leetcode-cn.com/problems/ones-and-zeroes/solution/yi-he-ling-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

细解过程

虽然,官方和评论都有涉及到背包类型的解释。但对小白来讲,并不直接,不是大白话,不好理解。

结合官方解释,先分析这张表是干嘛的?为什么4行3列?

m是0的个数,n是1的个数,题目最终要求m=3,n=2;

那么,从没遍历字符串, 到满足要求的一串 字符串,一边添加字符串,一边记录字符串中0和1个数的 所有情况:
0的个数情况:0,1,2 (超过不满足要求不考虑)
1的个数情况:0,1,2,3

遍历到第一字符串”10“

在这里插入图片描述
首先,遍历到“10”(绿色)这个字符串,这个字符串含有1个1,1个0;count[0]=1,count[1]=1。

加入该字符串,并考虑在后续遍历字符串时,出现0,1个数所有可能情况中,就是橙色部分(第一行,第一列情况并不存在了,为0,因为是基于在加入“10”之后的考虑情况 ),值为1 代表加入了这个字符串。

dp[zeroes][ones] = Math.max(1 + dp[zeroes - count[0]][ones - count[1]], dp[zeroes][ones]);

这个表达式表示:橙色框中对应的每个值,都是由原来初始 没加字符串前 的状态 加1,表示加入了该字符串。

例子:dp[zeroes][ones]=dp[2][2]:表示 在后续遍历字符串时,出现0,1个数是两个1,两个0的情况。加入 “10”(绿色)这个字符串 后值变为1,表示加入了 “10”。
那么此时这个状态 对应的前一状态 就是没加入“10”。
那么前一状态怎么表示?就是zeroes(=2)减去“10”这个字符串含有0的个数(=1),同理ones(=2)减去“10”这个字符串含有1的个数(=1)。也就是 没加入“10”,前一状态就是dp[1][1],即:dp[zeroes - count[0]][ones - count[1]]
关于为什么要比较取更大的那个,后面例子会解释

遍历到第二字符串”01“

这里开始遍历到第二个字符串"01"了(绿色)。先不考虑加入了“10”(前面的字符串),这里就是单独对一个字符串解析。加入该字符串,并考虑在后续遍历字符串时,出现0,1个数所有可能情况中,就是橙色部分(因为是基于在只加入“01”之后的考虑情况,这里一个0一个1,加入该字符串后,再加入或不加入其他字符串的存在可能情况就是:0和1的数量 都大于等于 一)
“01”--------------count[0]=1,count[1]=1。
这里就能解释为什么是这个循环条件了。
for (int zeroes = m; zeroes >= count[0]; zeroes–)
for (int ones = n; ones >= count[1]; ones–)
和前面一样的道理
在这里插入图片描述
dp[zeroes][ones] = Math.max(1 + dp[zeroes - count[0]][ones - count[1]], dp[zeroes][ones]);
继续讨论 循环体里面的情况。道理和前面加入字符串 差不多。
这里需要特别讨论加1后等于2的情况,为啥其他的不用呢?
除了基于代码的运算,结合实际意义,这里的解释是:现在是 到目前为止,我有(遍历)了 两个字符串:"10"和”01“。基于目前这个条件,结合题目
在这里插入图片描述
要求:该子集中 做多要3个0,1个1.(m=3,n=1),只能是"10"或者"01"的其中1个,不能两个都拿。

那么当要求:该子集中 做多要3个0,2个1.(m=3,n=2),就得两个都拿。
在这里插入图片描述
dp[zeroes][ones] = Math.max(1 + dp[zeroes - count[0]][ones - count[1]], dp[zeroes][ones]);
这里再来看看这个代码:我遍历了”10“之后,再遍历了”01“,
例子1:
dp[zeroes=3][ones=1] 时,它的前一状态是dp[2][0]=0,就是什么都没拿(”10“,"01"都没拿),而此状态dp[3][1]时的值是1,代表我之前已经拿了”10“。 那么现在两种选择:
(1)在 前一状态dp[2][0] 的基础上,拿”01“过去。即下面:
1 + dp[zeroes - count[0]][ones - count[1]=1
(2)保持此状态 dp[3][1] ,已经拿了”10“,不拿”01“
dp[zeroes][ones]=1
在这里插入图片描述
题目要找 最大子集的大小,当然 选两种情况的最大值,即拿的多的好。最终: dp[zeroes][ones]=1

**再来个例子:
dp[zeroes=3][ones=2] 时,它的前一状态是dp[2][1]=1,就是(”10“拿),而此状态dp[3][1]时的值是1,代表我之前已经拿了”10“。 那么现在两种选择:
(1)在 前一状态dp[2][1] 的基础上,拿”01“过去。即下面:
1 + dp[zeroes - count[0]][ones - count[1]=2
(2)保持此状态 dp[3][2] ,已经拿了”10“,不拿”01“
dp[zeroes][ones]=1

题目要找 最大子集的大小,当然 选两种情况的最大值,即拿的多的好。
最终:
dp[zeroes][ones] = Math.max(1 + dp[zeroes - count[0]][ones - count[1]], dp[zeroes][ones])=2
在这里插入图片描述

遍历到第三字符串”101“

这里开始遍历到第三个字符串"101"了(绿色)。先不考虑加入了“10” “01”(前面的字符串),这里就是单独对一个字符串解析。加入该字符串,并考虑在后续遍历字符串时,出现0,1个数所有可能情况中,就是橙色部分(因为是基于在只加入“101”之后的考虑情况,这里一个0两个1,加入该字符串后,再加入或不加入其他字符串的存在可能情况就是:0的数量 都大于等于 一,1的数量 都大于等于 两)
“101”--------------count[0]=1,count[1]=2。
这里就能解释为什么是这个循环条件了。
for (int zeroes = m; zeroes >= count[0]; zeroes–)
for (int ones = n; ones >= count[1]; ones–)
和前面一样的道理
在这里插入图片描述
dp[zeroes][ones] = Math.max(1 + dp[zeroes - count[0]][ones - count[1]], dp[zeroes][ones]);
这里再来看看这个代码:我遍历了”10“之后,再遍历了”01“,再遍历了”101“

例子1:
dp[zeroes=3][ones=2] 时,它的前一状态是dp[3-1=2][2-2=0]=0,就是什么都没拿(”10“,"01"都没拿),而此状态dp[3][2]时的值是2,代表我之前已经拿了”10““01”。 那么现在两种选择:
(1)在 前一状态dp[2][0] 的基础上,拿”101“过去。即下面:
1 + dp[zeroes - count[0]][ones - count[1]=1
(2)保持此状态 dp[3][2] ,已经拿了”10“”01“,不拿”101“
dp[zeroes][ones]=2
题目要找 最大子集的大小,当然 选两种情况的最大值,即拿的多的好。
最终:
dp[zeroes][ones] = Math.max(1 + dp[zeroes - count[0]][ones - count[1]], dp[zeroes][ones])=2

遍历到第四字符串”0“

这里开始遍历到第四个字符串"101"了(绿色)。先不考虑加入了“10” "01"“101”(前面的字符串),这里就是单独对一个字符串解析。加入该字符串,并考虑在后续遍历字符串时,出现0,1个数所有可能情况中,就是橙色部分(因为是基于在只加入“0”之后的考虑情况,这里一个0零个1,加入该字符串后,再加入或不加入其他字符串的存在可能情况就是:0的数量 都大于等于 一,1的数量 都大于等于 零)
“101”--------------count[0]=1,count[1]=0。
这里就能解释为什么是这个循环条件了。
for (int zeroes = m; zeroes >= count[0]; zeroes–)
for (int ones = n; ones >= count[1]; ones–)
和前面一样的道理
在这里插入图片描述
count[0]=1,count[1]=0。
dp[zeroes][ones] = Math.max(1 + dp[zeroes - count[0]][ones - count[1]], dp[zeroes][ones])
过程结果就是和前面的一样,不赘述了哈

最后解释下:为什么要倒序?从大到小
for (int zeroes = m; zeroes >= count[0]; zeroes–)
for (int ones = n; ones >= count[1]; ones–)
这里循环的意思是:先行不变,行数从3开始,先变列数。再动行数。计算顺序和结果如下:(官方正确结果)
dp[zeroes][ones] = Math.max(1 + dp[zeroes - count[0]][ones - count[1]], dp[zeroes][ones])
此时 dp[zeroes][ones] = Math.max(1 + dp[zeroes - 1][ones - 0], dp[zeroes][ones])
在这里插入图片描述
下面是原始图的操作过程,对着看
在这里插入图片描述
表示着,从原来遍历了”10“”01“”101“后,再遍历”0“后 最大子集的大小 情况

例子1:
dp[zeroes=3][ones=2] 时,它的前一状态是dp[3-1=2][2-0=2]=2,就是”10“,"01"都拿了(”101“没拿直观是因为"101"与”10“ 和 "101"与”01“ 构成的数集中 1的个数大于2了,不符合 前一状态是dp[2][2] 。而只拿”101“是dp[1][2]的状态 ),而此状态dp[3][2]时的值是2,代表我之前已经拿了”10““01”。 那么现在两种选择:
(1)在 前一状态dp[2][2] 的基础上,拿”0“过去。即下面:
1 + dp[zeroes - count[0]][ones - count[1]=3
(2)保持此状态 dp[3][2] ,已经拿了”10“”01“,不拿”0“
dp[zeroes][ones]=2
题目要找 最大子集的大小,当然 选两种情况的最大值,即拿的多的好。
最终:
dp[zeroes][ones] = Math.max(1 + dp[zeroes - count[0]][ones - count[1]], dp[zeroes][ones])=3

接着再来看看不倒序?从小到大,会发生什么
原来的
for (int zeroes = m; zeroes >= count[0]; zeroes–)
for (int ones = n; ones >= count[1]; ones–)
变为
for (int zeroes = count[0]; zeroes <= m; zeroes++)
for (int ones = count[1]; ones <=n ; ones++)
这里循环的意思是:先行不变,行数从1开始,先变列数。再动行数。
先看看运算顺序:
在这里插入图片描述
计算过程:
在这里插入图片描述
最终结果就是白色圈里的,显然不对,”0“字符串被多次添加拿过去。不合题意。解释: dp[zeroes][ones] = Math.max(1 + dp[zeroes - count[0]][ones - count[1]], dp[zeroes][ones])。
count[0]>=0,count[1]>=0; 数据更新,肯定是从前面的列或行中取数+1;从前往后(从小到大)的方式肯定会使字符串被多次添加拿过去

原来的:先行不变,行数从3开始,先变列数。再动行数。
for (int zeroes = m; zeroes >= count[0]; zeroes–)
for (int ones = n; ones >= count[1]; ones–)
变为:先列不变,列数从2开始,先变行数。再动列数。
for (int ones = n; ones >= count[1]; ones–)
for (int zeroes = m; zeroes >= count[0]; zeroes–)

结果是一样的,不信可以力扣试试,只要从大到小就不会出错!!

最后再看 代码 就顺眼很多, 本小白 终于理解了,也更容易记忆和自己写

官方代码

public class Solution {
    public int findMaxForm(String[] strs, int m, int n) {
        int[][] dp = new int[m + 1][n + 1];
        for (String s: strs) {
            int[] count = countzeroesones(s);
            for (int zeroes = m; zeroes >= count[0]; zeroes--)
                for (int ones = n; ones >= count[1]; ones--)
                    dp[zeroes][ones] = Math.max(1 + dp[zeroes - count[0]][ones - count[1]], dp[zeroes][ones]);
        }
        return dp[m][n];
    }
    public int[] countzeroesones(String s) {
        int[] c = new int[2];
        for (int i = 0; i < s.length(); i++) {
            c[s.charAt(i)-'0']++;
        }
        return c;
    }
}


作者:LeetCode
链接:https://leetcode-cn.com/problems/ones-and-zeroes/solution/yi-he-ling-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值