力扣-474. 一和零

1.题目

题目地址(474. 一和零 - 力扣(LeetCode))

https://leetcode.cn/problems/ones-and-zeroes/

题目描述

给你一个二进制字符串数组 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

2.题解

2.1 三维dp数组

思路

这里设计一个三维dp数组dp[i][j][k], 其中i表示在[0,i-1]中选择任意个数, j表示最多容忍的0的个数,k表示最多容忍的1的个数;而dp的值则记录已经选择了字符串的最大可能个数!
对于每一个字符串,我们转换为0-1背包问题,可以选择 选 或者 不选:
1.不选的话就是dp[i][j][k] = dp[i-1][j][k], 从之前的进行继承
2.选的话就是:
2.1 如果 j > zeros, k > ones, 说明最大容忍个数允许这个字符串作为子集的一份子加入,
所以我们dp[i][j][k] = max(dp[i][j][k], dp[i - 1][j - zeros][k - ones] + 1);, 保留原值和更新值(加了一个字符串,不要忘记+1)中的较大值即可
2.2 反之我们根本无法加入该字符串到当前子集中,所以无需考虑,跳过即可

代码

  • 语言支持:C++

C++ Code:

class Solution {
public:
    // 计算当前字符串中0和1的数量
    vector<int> getZerosOnes(string& str) {
        vector<int> count(2);
        for (char ch : str) {
            count[ch - '0']++;
        }
        return count;
    }

    int findMaxForm(vector<string>& strs, int m, int n) {
        int len = strs.size();
        // dp数组记录当前[0,i-1]范围中,0个数为j,1个数为k情况下选择的字符串个数
        vector<vector<vector<int>>> dp(len + 1, vector<vector<int>>(m + 1, vector<int>(n + 1, 0)));

        // 更新dp数组(从1开始,之前已有隐式初始化所有i=0的值为0)
        for (int i = 1; i <= len; i++) {
            string str = strs[i - 1]; // 这里strs索引依旧是从0开始,所以不要忘记-1
            vector<int> count = getZerosOnes(str);
            int zeros = count[0], ones = count[1];
            for (int j = 0; j <= m; j++) {
                for (int k = 0; k <= n; k++) {
                    // 如果不选择该字符串,保留原字符串个数
                    dp[i][j][k] = dp[i - 1][j][k];
                    // 如果选择字符串,可能遇到更新的dp已经存在值
                    // 这时候我们选取字符串个数多的保留即可(注意这里选择了该字符串,不要忘记了+1)
                    if (j >= zeros && k >= ones) {
                        dp[i][j][k] = max(dp[i][j][k], dp[i - 1][j - zeros][k - ones] + 1);
                    }
                }
            }
        }
        return dp[len][m][n];
    }
};

复杂度分析

令 n 为数组长度。

  • 时间复杂度:\(O(lmn+L)\)
    其中\(l\)是数组strs的长度,\(m\)\(n\)分别是 0 和 1 的容量,\(L\)是数组strs中的所有字符串的长度之和。
    动态规划需要计算的状态总数是\(O(lmn)\),每个状态的值需要\(O(1)\)的时间计算。
    对于数组strs中的每个字符串,都要遍历字符串得到其中的0 和 1 的数量,因此需要\(O(L)\)的时间遍历所有的字符串。
    总时间复杂度是\(O(lmn+L)\)
  • 空间复杂度:\(O(lmn)\)

2.2 二维dp数组(空间优化)

思路

同416-分割等和子集,我们这里发现其实对于dp[i]... = dp[i-1]..., 我们每次只需要记录上一次的dp值即可,而不要记录每一个i对应的dp值
所以这里我们只需要dp[j][k]即可解决问题。

但是这里我们需要注意一个问题:如果我们正序遍历,由于dp[j][k] = dp[j-zeros][k-ones]的存在,这里更大的序列值优先被更新;
等我们遍历到这些较大序列dp,会发现它的值已经不是上一次存储的值,而在之前已经被更新了;
为了避免这种情况,我们选择倒序遍历,便可以完美解决问题!

代码

class Solution {
public:
    // 计算当前字符串中0和1的数量
    vector<int> getZerosOnes(string& str) {
        vector<int> count(2);
        for (char ch : str) {
            count[ch - '0']++;
        }
        return count;
    }

    int findMaxForm(vector<string>& strs, int m, int n) {
        int len = strs.size();
        vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));

        // 更新dp数组(从1开始,之前已有隐式初始化所有i=0的值为0)
        for (int i = 1; i <= len; i++) {
            string str = strs[i - 1]; // 这里strs索引依旧是从0开始,所以不要忘记-1
            vector<int> count = getZerosOnes(str);
            int zeros = count[0], ones = count[1];
            for (int j = m; j >= 0; j--) {
                for (int k = n; k >= 0; k--) {
                    // 如果选择字符串,可能遇到更新的dp已经存在值
                    // 这时候我们选取字符串个数多的保留即可(注意这里选择了该字符串,不要忘记了+1)
                    if (j >= zeros && k >= ones) {
                        dp[j][k] = max(dp[j][k], dp[j - zeros][k - ones] + 1);
                    }
                }
            }
        }
        return dp[m][n];
    }
};

复杂度分析

令 n 为数组长度。

  • 时间复杂度:\(O(lmn+L)\)
    其中\(l\)是数组strs的长度,\(m\)\(n\)分别是 0 和 1 的容量,\(L\)是数组strs中的所有字符串的长度之和。
    动态规划需要计算的状态总数是\(O(lmn)\),每个状态的值需要\(O(1)\)的时间计算。
    对于数组strs中的每个字符串,都要遍历字符串得到其中的0 和 1 的数量,因此需要\(O(L)\)的时间遍历所有的字符串。
    总时间复杂度是\(O(lmn+L)\)
  • 空间复杂度:\(O(mn)\)
posted @ 2024-05-30 09:31  DawnTraveler  阅读(6)  评论(0编辑  收藏  举报