ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

474. 一和零

2021-10-06 12:32:01  阅读:142  来源: 互联网

标签:index int LEN strs num 474 left


474. 一和零

474. 一和零

我的思路

题意分析

相当于给你一个数组 s t r s = [ ( a 0 , b 0 ) , ( a 1 , b 1 ) … ( a l − 1 , b l − 1 ) ] strs = [(a_0,b_0), (a_1, b_1) \ldots (a_{l-1}, b_{l-1})] strs=[(a0​,b0​),(a1​,b1​)…(al−1​,bl−1​)], a i a_i ai​是 0 的个数, b i b_i bi​是 1 的个数,数组长度为 l l l。

同时给你两个数字 ( m , n ) (m, n) (m,n),求从 s t r s strs strs中选出来 k k k 组,使得 k k k 组的所有 a a a 相加小于等于 m m m,并且 b b b 相加小于等于 n n n,求最大的 k k k。

别的不会,先写个爆搜

我们定义 dfs(int index, int left_m, int left_n) 代表 当前 我们搜索到了数组的 i n d e x index index 号元素,同时剩余可以用的 0 和 1 的个数分别是 left_mleft_n。函数的返回结果是,根据上述条件,能够获得的最大的 k k k。

对于第 i n d e x index index 号元素:

  • 我们可以不选,那么,继续搜索 dfs(index+1, left_m, left_n)
  • 只有当 a i n d e x ≤ l e f t _ m a n d b i n d e x ≤ l e f t _ n a_{index} \le left\_m \quad and \quad b_{index} \le left\_n aindex​≤left_mandbindex​≤left_n时,我们才能选择要 :dfs(index+1, left_m-a_index, left_n-b_index)

因此,当前的返回值是选与不选两种情况的最大值

当 i n d e x = s t r s . s i z e ( ) index = strs.size() index=strs.size() 时,返回 0 即可。

写出代码

class Solution {
public:
    vector<pair<int, int>> cnt;
    int LEN = 0;    
    int findMaxForm(vector<string>& strs, int m, int n) {
        LEN = strs.size();
        memset(f, -1, sizeof(f));
        for (auto& str : strs){
            int num_0 = 0, num_1 = 0;
            for (auto ch : str){
                if (ch == '0'){
                    num_0 ++;
                }else if (ch == '1'){
                    num_1 ++;
                }
            }
            cnt.emplace_back(pair<int, int>(num_0, num_1));
        }

        return dfs(0, m, n);
    }

    int dfs(int index, int left_m, int left_n){
        if (index >= LEN){
            return 0;
        }
        int a = dfs(index+1, left_m, left_n);
        int b = 0;
        if (left_m - cnt[index].first >= 0 && left_n - cnt[index].second >= 0){
            b = dfs(index+1, left_m - cnt[index].first, left_n - cnt[index].second) + 1;
        }
        
        int ret = max(a, b);
        return ret;
    }
};

记忆化搜索

显然,写出来后,发现indexleft_mleft_n三个变量在搜索中可能会出现重复,于是使用一个三维数组记录该三个变量。

记忆化搜索嘛,一般会AC(笑)。那这题一定也能用动态规划喽~

AC代码如下:

class Solution {
public:
    vector<pair<int, int>> cnt;
    int LEN = 0;
    int f[602][102][102];
    int findMaxForm(vector<string>& strs, int m, int n) {
        LEN = strs.size();
        memset(f, -1, sizeof(f));
        for (auto& str : strs){ // 预处理
            int num_0 = 0, num_1 = 0;
            for (auto ch : str){
                if (ch == '0'){
                    num_0 ++;
                }else if (ch == '1'){
                    num_1 ++;
                }
            }
            cnt.emplace_back(pair<int, int>(num_0, num_1));
        }

        return dfs(0, m, n);
    }

    int dfs(int index, int left_m, int left_n){
        if (index >= LEN){
            return 0;
        }
        if (f[index+1][left_m][left_n] != -1){  // 剪枝,如果数组中存在,那么直接返回
            return f[index+1][left_m][left_n];
        }
        int a = dfs(index+1, left_m, left_n);
        int b = 0;
        if (left_m - cnt[index].first >= 0 && left_n - cnt[index].second >= 0){
            b = dfs(index+1, left_m - cnt[index].first, left_n - cnt[index].second) + 1;
        }
        
        f[index+1][left_m][left_n] = max(a, b); // 记录在数组中
        return f[index+1][left_m][left_n];
    }
};

时间复杂度: O ( L E N ⋅ m ⋅ n + ∑ i = 0 L E N − 1 s i z e ( s t r s i ) ) O(LEN \cdot m \cdot n + \sum_{i=0}^{LEN-1}size(strs_i)) O(LEN⋅m⋅n+∑i=0LEN−1​size(strsi​)),显然,一种最坏的情况是:每一个位置都有 m 和 n 个剩余,那么dfs的时间复杂度是 O ( L E N ⋅ m ⋅ n ) O(LEN \cdot m \cdot n) O(LEN⋅m⋅n),同时,预处理需要遍历 s t r s strs strs中的每一个字符,时间复杂度是 O ( ∑ i = 0 L E N − 1 s i z e ( s t r s i ) ) O(\sum_{i=0}^{LEN-1}size(strs_i)) O(∑i=0LEN−1​size(strsi​))

空间复杂度: O ( L E N ⋅ m ⋅ n ) O(LEN \cdot m \cdot n) O(LEN⋅m⋅n) , 一个 f 数组占用 O ( L E N ⋅ m ⋅ n ) O(LEN \cdot m \cdot n) O(LEN⋅m⋅n), 一个cnt 占用 O ( L E N ) O(LEN) O(LEN), 因此是 O ( L E N ⋅ m ⋅ n ) O(LEN \cdot m \cdot n) O(LEN⋅m⋅n)。其实是 O ( 1 ) O(1) O(1),因为f大小固定(笑)

动态规划

有了上述 记忆化搜索的经验,我们可以轻松地定义动态规划的数组,转义状态,以及初始条件。

数组定义

定义 f[i][j][k] 表示 数组的前 i i i个元素组成的新数组, 0 的可用个数是 j j j, 1 的可用个数是 k k k,能够获得的最大的个数。

显然,最终结果是 : f[LEN][m][n]LEN 是 s t r s strs strs 的大小。

状态转移

我们记第 i i i 个元素的 0 的个数是 n u m 0 num_0 num0​,1 的个数是 n u m 1 num_1 num1​。

当第 i i i 号元素可以选择时,即 j ≥ n u m 0   &   k ≥ n u m 1 j \ge num_0\ \And \ k \ge num_1 j≥num0​ & k≥num1​,此时 f [ i ] [ j ] [ k ] = m a x ( f [ i − 1 ] [ j ] [ k ] , f [ i − 1 ] [ j − n u m 0 ] [ k − n u m 1 ] + 1 ) f[i][j][k] = max(f[i-1][j][k], f[i-1][j-num_0][k-num_1] + 1) f[i][j][k]=max(f[i−1][j][k],f[i−1][j−num0​][k−num1​]+1)

当第 i i i 号元素无法选择时,即 j < n u m 0   ∣   k < n u m 1 j \lt num_0\ | \ k \lt num_1 j<num0​ ∣ k<num1​,此时 f [ i ] [ j ] [ k ] = f [ i − 1 ] [ j ] [ k ] f[i][j][k] = f[i-1][j][k] f[i][j][k]=f[i−1][j][k]

转移方程:
f [ i ] [ j ] [ k ] = { m a x ( f [ i − 1 ] [ j ] [ k ] , f [ i − 1 ] [ j − n u m 0 ] [ k − n u m 1 ] + 1 ) , j ≥ n u m 0   &   k ≥ n u m 1 f [ i − 1 ] [ j ] [ k ] , j < n u m 0   ∣   k < n u m 1 f[i][j][k] = \begin{cases} max(f[i-1][j][k], f[i-1][j-num_0][k-num_1] + 1),&j \ge num_0\ \And \ k \ge num_1\\ f[i-1][j][k],&j \lt num_0\ | \ k \lt num_1 \end{cases} f[i][j][k]={max(f[i−1][j][k],f[i−1][j−num0​][k−num1​]+1),f[i−1][j][k],​j≥num0​ & k≥num1​j<num0​ ∣ k<num1​​

初始条件

显然,当 i = 0 i=0 i=0 时,新数组是空的,此时对于任何的 m 或 n, 都有 f[0][*][*] = 0

写出代码

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        vector<pair<int, int>> cnt;
        int LEN = strs.size();
        for (auto& str : strs){ // 预处理
            int num_0 = 0, num_1 = 0;
            for (auto ch : str){
                if (ch == '0'){
                    num_0 ++;
                }else if (ch == '1'){
                    num_1 ++;
                }
            }
            cnt.emplace_back(pair<int, int>(num_0, num_1));
        }

        int f[602][102][102] = {0};

        for (int i = 1; i <= LEN; i ++){
            for (int j = 0; j <= m; j ++){
                for (int k = 0; k <= n; k ++){
                    if (cnt[i-1].first <= j && cnt[i-1].second <= k){
                        f[i][j][k] = max(f[i-1][j][k], f[i-1][j - cnt[i-1].first][k - cnt[i-1].second] + 1);
                    }else{
                        f[i][j][k] = f[i-1][j][k];
                    }
                }
            }
        }
        return f[LEN][m][n];
    }
};

时间复杂度: O ( L E N ⋅ m ⋅ n + ∑ i = 0 L E N − 1 s i z e ( s t r s i ) ) O(LEN \cdot m \cdot n + \sum_{i=0}^{LEN-1}size(strs_i)) O(LEN⋅m⋅n+∑i=0LEN−1​size(strsi​))
空间复杂度: O ( L E N ⋅ m ⋅ n ) O(LEN \cdot m \cdot n) O(LEN⋅m⋅n)

优化空间复杂度

显然,我们可以将第一维度用 滚动数组的形式优化掉

如果我们将第一维度去掉,代码会变成:

if (cnt[i-1].first <= j && cnt[i-1].second <= k){
    f[j][k] = max(f[j][k], f[j - cnt[i-1].first][k - cnt[i-1].second] + 1);
}

显然,在计算f[k][j]时,需要保证f[j - cnt[i-1].first][k - cnt[i-1].second] + 1未被更新,因此内两层循环都需要倒序遍历

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        vector<pair<int, int>> cnt;
        int LEN = strs.size();
        for (auto& str : strs){ // 预处理
            int num_0 = 0, num_1 = 0;
            for (auto ch : str){
                if (ch == '0'){
                    num_0 ++;
                }else if (ch == '1'){
                    num_1 ++;
                }
            }
            cnt.emplace_back(pair<int, int>(num_0, num_1));
        }

        int f[102][102] = {0};

        for (int i = 1; i <= LEN; i ++){
            for (int j = m; j >= 0; j --){ // 倒序
                for (int k = n; k >= 0; k --){  // 倒序
                    if (cnt[i-1].first <= j && cnt[i-1].second <= k){
                        f[j][k] = max(f[j][k], f[j - cnt[i-1].first][k - cnt[i-1].second] + 1);
                    }
                }
            }
        }
        return f[m][n];
    }
};

时间复杂度: O ( L E N ⋅ m ⋅ n + ∑ i = 0 L E N − 1 s i z e ( s t r s i ) ) O(LEN \cdot m \cdot n + \sum_{i=0}^{LEN-1}size(strs_i)) O(LEN⋅m⋅n+∑i=0LEN−1​size(strsi​))
空间复杂度: O ( m ⋅ n ) O(m \cdot n) O(m⋅n)


2021.10.06 12:15

昨天把《Flask Web开发实战——入门、进阶与原理解析》,也就是李辉大神所著的“狼书”看完了一遍,虽然后面的Flask原理没咋看明白。
囫囵吞枣式地看了一遍,了解了很多工程方面的新知识,不能说学到了,只能说知晓了。

昨日,进行的工作还有:写了一篇博客;看Fluent Python一书第5章;做外包的活儿;做AML crawler的事情,可算完成了一小部分;洗了衣服和床上用品;等。

准备学习一下 vue ,貌似 在 jinja2 和 vue 之间我要做出抉择。

小孩子才做选择,成年人的世界就是:我都要(笑)。

身体力行,获得新知。

标签:index,int,LEN,strs,num,474,left
来源: https://blog.csdn.net/Histranger_/article/details/120623427

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有