leetcode
leetcode 401 ~ 450
一和零

一和零

难度:

标签:

题目描述

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

请你找出并返回 strs 的最大子集的长度,该子集中 最多m0n1

如果 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

代码结果

运行时间: 61 ms, 内存: 16.3 MB


/*
题目思路:
使用Java Stream,我们可以首先通过流来计算每个字符串中0和1的数量,然后通过动态规划的思想来更新dp数组。
*/
 
import java.util.stream.Stream;
 
public class Solution {
    public int findMaxForm(String[] strs, int m, int n) {
        int[][] dp = new int[m + 1][n + 1];
        Stream.of(strs)
            .map(str -> new int[]{(int)str.chars().filter(c -> c == '0').count(), (int)str.chars().filter(c -> c == '1').count()})
            .forEach(counts -> {
                for (int i = m; i >= counts[0]; i--) {
                    for (int j = n; j >= counts[1]; j--) {
                        dp[i][j] = Math.max(dp[i][j], dp[i - counts[0]][j - counts[1]] + 1);
                    }
                }
            });
        return dp[m][n];
    }
}

解释

方法:

这个题解使用动态规划的思路来解决问题。首先将字符串数组 strs 中的每个字符串统计出其中 0 和 1 的个数,并将结果保存在 mn 数组中。然后对 mn 数组按照 0 的个数从小到大进行排序。接下来使用一个二维数组 d 来记录动态规划的状态,其中 d[i][0] 表示当前能够达到的最小的 0 的个数,d[i][1] 表示在 d[i][0] 个 0 的情况下,最多能够包含的 1 的个数。遍历 mn 数组,对于每个字符串,更新 d 数组的状态。最后遍历 d 数组,找出最大的 d[i][1] 作为最终结果返回。

时间复杂度:

O(mn + nlogn)

空间复杂度:

O(mn)

代码细节讲解

🦆
题解中提到将mn数组按照0的个数从小到大排序,这种排序策略对解题有什么具体的帮助?
将mn数组按照0的个数从小到大排序有助于优先处理使用较少0的字符串。这种策略在动态规划中是有益的,因为它允许我们在不超过m的限制下尽可能地包含更多的字符串。这样的排序确保了在遍历字符串的过程中,我们可以先尝试添加对资源消耗较低的字符串,这可能增加在不超过m和n限制的情况下可以选择的字符串总数。
🦆
动态规划数组d的定义中,d[i][0]表示最小的0的个数,而d[i][1]表示最多的1的个数。这样的定义在更新状态时如何确保每步都是最优的?
在动态规划中,每一步的状态更新都是基于之前的最优解进行计算的。通过维护d[i][0]作为当前可用的最小0的个数和d[i][1]作为在这种0的消耗下可获得的最大1的数量,我们确保在每一步都考虑了到达当前状态的最佳路径。这种设置允许我们在每一步决策时,都是基于当前可用的最优资源配置来进行更新,从而确保整个过程的每一步都是局部最优解,进而推导出全局最优解。
🦆
题解中动态规划的更新是从后往前进行的,请问这样做的目的是什么?
在动态规划中从后往前更新的主要目的是为了防止在更新过程中覆盖还未使用到的状态。这是因为每个状态的更新可能依赖于当前阶段的其他状态,如果从前向后更新,那么可能会导致某些状态被早期的更新错误地覆盖,从而影响最终结果的正确性。通过从后往前更新,我们可以确保在更新当前状态时,依赖的状态仍然是在这一轮更新之前的状态,从而保持更新的正确性。
🦆
循环中有一个条件判断`if mn[i][0] > m: break`,这里直接跳出循环是否意味着后面的字符串都不会被考虑?这样处理是否可能遗漏某些有效的字符串组合?
这个条件判断确实意味着如果某个字符串的0的数量超过m,那么这个字符串及所有排序后的后续字符串都不会被考虑,因为它们的0的数量只会更多。这种处理方式基于mn数组已经按照0的数量排序的前提,确保了一旦某个字符串的0数量超过了m,后续的字符串也同样不可能被包含在任何有效的组合中(因为它们需要的0的数量更多)。因此,这种处理方式并不会遗漏有效的字符串组合,而是一种效率上的优化。

相关问题

不含连续1的非负整数

给定一个正整数 n ,请你统计在 [0, n] 范围的非负整数中,有多少个整数的二进制表示中不存在 连续的 1

 

示例 1:

输入: n = 5
输出: 5
解释: 
下面列出范围在 [0, 5] 的非负整数与其对应的二进制表示:
0 : 0
1 : 1
2 : 10
3 : 11
4 : 100
5 : 101
其中,只有整数 3 违反规则(有两个连续的 1 ),其他 5 个满足规则。

示例 2:

输入: n = 1
输出: 2

示例 3:

输入: n = 2
输出: 3

 

提示:

  • 1 <= n <= 109