代码随想录day37 动态规划(3)

416. 分割等和子集 - 力扣(LeetCode) 

解1:二维dp数组,时间O(m*n),空间O(m*n),m、n为dp数组的行和列数。

判断原数组总和能否整除2;

将target设为total // 2(若是total / 2,target为float,需要转为int才能以target为维度新建dp数组);

* 新建dp,行数 = nums长度,列数 = target+1,i行j列dp[i][j]表示从子数组nums[0:i+1](第0~第i个数)不重复地取数,能够获得的不超过j的最大总和。dp[len(nums)-1][target]若等于target,说明在整个nums中能够不重复地取出总和恰好为target的数,返回true。

* 考虑dp[i][j]的计算:若当前数nums[i] > j,说明i不可加入,于是dp[i][j] = dp[i-1][j]。否则,i可能加入,获得总和 = dp[i-1][j-nums[i]] + nums[i](将加入了i数的背包想象成两部分,一部分是i数本身,价值和重量都为i,另一部分不含i而且重量上限为j-nums[i],dp[i-1][j-nums[i]]就是第二部分能取到的最大价值);i也可以选择不加入,获得总和 = dp[i-1][j]。用i加入/不加入中的较大者更新dp[i][j]。

由此可见,需要通过dp[i][j]左侧及上侧的值计算它。遍历时从左到右,从上到下。

* base情况,考虑左、上边缘的值:1)dp[x][0]表示总和上限=0,因此dp[x][j]=0(dp本来都初始化为0,不需处理);2)dp[0][x]表示只考虑nums第0个数,则当x小于nums[0],初始为0,当x不小于nums[0],都初始化为nums[0]。

遍历每行、每列,直到右下角。

class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        if len(nums) <= 1:
            return False

        total = sum(nums)
        if total % 2 != 0:
            return False
        target = total // 2
        
        #dp[i][j]:从nums的[0, i]中取数,能获得的不超过j的最大总和.dp: len(nums) * (target+1)
        dp = [[0 for _ in range(target+1)] for _ in range(len(nums))]
        #转移:dp[i][j] = max(dp[i-1][j], dp[i][j-nums[i]]+nums[i])
        #base:不超过0的最大总和:只能不取任何数,dp[x][0] = 0, 
        #      只考虑第0个数时:dp[0][x] = nums[0] if x >= nums[0] else = 0
        for x in range(nums[0], target+1):
            dp[0][x] = nums[0]

        for j in range(1, target+1):#最大和=j
            for i in range(1, len(nums)):#取0~i个数
                if nums[i] > j:
                    dp[i][j] = dp[i-1][j]
                else:
                    dp[i][j] = max(dp[i-1][j], dp[i-1][j-nums[i]]+nums[i])
        
        return dp[-1][-1] == target

解2:压缩为一维dp数组,时间O(m*n),空间O(n)

将物品/nums[i]维度压缩掉了,dp是长度=target+1的一维数组,按nums[0]的情况初始化dp。由于新的dp[j]需要参考“上一行左侧”或“上一行正上方”的值,应该从后往前遍历dp,防止上一行的值在使用之前被新的值覆盖。若当前数nums[i]超过当前上限j,不改变dp[j]的值(相等于沿用上一行正上方的值);否则,用当前值dp[j]与dp[j-nums[i]] + nums[i]的较大者更新当前值。

class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        if len(nums) <= 1:
            return False

        total = sum(nums)
        if total % 2 != 0:
            return False
        target = total // 2
        
        dp = [0 for _ in range(target+1)]
        for i in range(1, target+1):
            if nums[0] <= i:
                dp[i] = nums[0]
        
        for i in range(1, len(nums)):
            for j in range(target, 0, -1):
                if nums[i] <= j:
                    dp[j] = max(dp[j], dp[j-nums[i]] + nums[i])
        
        return dp[-1] == target

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值