动态规划--leecode 474---python

474. 一和零

难度中等256

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

解题思路:递归、动态规划

一、递归

递归代码性能分析:(暴力穷举,时间复杂度O(2^k),超时)

递归思想: 情况一:如果当前子串符合条件,则可以加入进行计算,长度计数器加一,并且m、n减去当前子串的0、1个数

                   情况二:无论当前子串符不符合条件,都不将该子串加入,直接进行下一子串进行运算,digui(k + 1, m, n, length)

                   再比较情况一和情况二的最大长度

重要代码:if m - strs[k].count('0') >= 0 and n - strs[k].count('1') >= 0:
                      a = digui(k + 1, m - strs[k].count('0'), n - strs[k].count('1'), length + 1)
                 if m>0 or n>0:
                       b = digui(k + 1, m, n, length)

                max(a,b)

def findMaxForm(strs, m: int, n: int) -> int:
    strs.sort(key=lambda x:len(x))
    dp=[[[0 for k in range(n+1) ] for j in range(m+1)] for i in range(len(strs)+1)]
    # k代表当前第k个子串,m、n表示当前还能存放0、1的个数,length表示当前符合规则子串的个数
    def digui(k, m, n, length):
        a, b = 0, 0
        if m <= 0 or n <= 0 or k == len(strs):
            return length
        if m - strs[k].count('0') >= 0 and n - strs[k].count('1') >= 0:
            a = digui(k + 1, m - strs[k].count('0'), n - strs[k].count('1'), length + 1)
        if m>0 or n>0:
            b = digui(k + 1, m, n, length)
        return max(a, b)
#测试数据
findMaxForm(["10","0001","111001","1","0"],5,3)

二、动态规划:

    思想: 就是将递归的思想抽象后,进行填表,建立一张[k][m][n]的表,将(加入当前子串的情况)与(上一子串0,1表情况)进行比较,取大的值填表。

     加入当前子串的情况:dp[i-1][j-strs[i-1].count('0')][k-strs[i-1].count('1')]+1  ,j-strs[i-1].count('0')表示当前子串消耗0的个数后的情况,k-strs[i-1].count('1')表示当前子串消耗1的个数后的情况,+1是因为考虑了当前子串所以长度加一。

    不加入当前子串情况:dp[i-1][j][k],直接取上一个子串表当前ij的值即可

def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
        dp=[[[0 for k in range(n+1) ] for j in range(m+1)] for i in range(len(strs)+1)]
        for i in range(1,len(strs)+1):
            for j in range(0,m+1):
                for k in range(0,n+1):
                    if strs[i-1].count('0') <= j and strs[i-1].count('1') <=k:
                        dp[i][j][k] = max(dp[i-1][j][k],dp[i-1][j-strs[i-1].count('0')][k-strs[i-1].count('1')]+1)
                    else:
                        dp[i][j][k] = dp[i - 1][j][k]            
        return(dp[len(strs)][m][n])

时间复杂度分析:O(k*(m+1)*(n+1))

结果:依然超时

三、优化:

         1、空间优化:分析动态规划过程,是将每一个子串都建一个表,然后在0<=i<=m,0<=j<=n时, 都判断加入当前子串与不加入当前子串的长度哪个更长,是根据上一子串的表的数据进行判断,所以我们只需要建立一个(m+1)*(n+1)的表,判断时就依据该表的元素进行判断,然后每次修改该表即可。

           2、时间优化:从j=m、k=n向下标0,0进行填表,当 j<当前子串的0的个数k<当前子串的1的个数,因为是逆向填表,那么继续迭代时j,k只会更小,后面的jk都不会满足当前子串0,1个数的条件,则该子串填表结束,节省了填表时间。

 def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
        dp=[[0 for j in range(n+1)] for i in range(m+1)]
        for i in range(1,len(strs)+1):
            if m > strs[i - 1].count('0') - 1 and n > strs[i - 1].count('1') - 1:
                for j in range(m,strs[i-1].count('0')-1,-1):
                    for k in range(n,strs[i-1].count('1')-1,-1):
                        dp[j][k] = max(dp[j][k],dp[j-strs[i-1].count('0')][k-strs[i-1].count('1')]+1)
        return(dp[m][n])

优化后:通过所以用例

优化优化再优化:1、比较操作优化,max比if操作更耗时,

                             2、计算当前子串中0,1个数优化:上一程序每次循环都去计算0,1个数,计算了i*k*j次,我们可以将每层循环只计算1次,记录下来然后使用,代码如下。

class Solution:
    def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
        dp=[[0 for j in range(n+1)] for i in range(m+1)]
        for i in range(1,len(strs)+1):
            zeros = strs[i-1].count('0')
            ones = strs[i-1].count('1')
            if m > strs[i - 1].count('0') - 1 and n > strs[i - 1].count('1') - 1:
                for j in range(m,zeros-1,-1):
                    for k in range(n,ones-1,-1):
                        if dp[j][k]< dp[j-zeros][k-ones]+1:
                            dp[j][k] =dp[j-zeros][k-ones]+1
        return(dp[m][n])

最终效果:可见时间缩小为之前的1/4

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值