LeetCode 416:分割等和子集(转化为0-1背包问题)

题目描述

在这里插入图片描述

问题转化

可以理解成是否能从给定数组里取出部分元素,使这些元素总和刚好为数组总和的一半。由于每个元素只能使用一次,所以就是0-1背包问题了。

二维数组解法

  1. 确定dp数组及下标含义
    dp[i][j]表示是否能从下标不大于i的元素中取出部分元素使得这些元素总和刚好为j(值为True或False)。j的范围是0~target,target就是数组总和的一半。
  2. 递推公式
    dp[i][j]=dp[i-1][j] or dp[i-1][j-nums[i]]
    两种选择:
    1)不使用下标为i的元素,此时dp[i][j]=dp[i-1][j]
    2)使用下标为i的元素,此时dp[i][j]=dp[i-1][j-nums[i]]
    这两种选择满足其一即为True,所以用or运算符连接。
  3. 初始化
    假如数组为[1,2,3],target=3,则数组如下。
dp = [[False] * n for i in range(m)]

在这里插入图片描述
首先将第1列初始化为True,可以理解成当target为0时,一个元素都不取,这也算一种选择。
在这里插入图片描述
然后初始化第一行,递推公式为
dp[0][j] = dp[0][j - nums[0]]
这里要用倒序遍历,因为每个元素只能用一次。
在这里插入图片描述

  1. 双重遍历填表
    在这里插入图片描述
    完整代码如下:
def canPartition(self, nums: List[int]) -> bool:
        total = sum(nums)
        if total % 2 == 1 or len(nums) == 1:
            return False
        m = len(nums)
        n = total // 2 + 1
        dp = [[False] * n for i in range(m)]
        for i in range(m):
            dp[i][0] = True
        for j in range(n - 1, 0, -1):
            if j >= nums[0]:
                dp[0][j] = dp[0][j - nums[0]]
        for i in range(1, m):
            for j in range(1, n):
                if j < nums[i]:
                    dp[i][j] = dp[i - 1][j]
                else:
                    dp[i][j] = dp[i - 1][j] or dp[i - 1][j - nums[i]]
        return dp[i][j]

滚动数组

因为当前值只和上一层有关,所以我们可以用滚动数组代替原来的二维数组来降低空间复杂度。
所谓的滚动数组就是不断用当前层的值覆盖上一层的值,所以要注意在内层循环中一定要逆序。不然当左边的值被新的值覆盖后,再计算后面的值时就会出错。

def canPartition(self, nums: List[int]) -> bool:
        total = sum(nums)
        if total % 2 == 1 or len(nums) == 1:
            return False
        m = len(nums)
        n = total // 2 + 1
        dp = [False] * n
        dp[0] = True
        for i in range(m):
            for j in range(n - 1, 0, -1):
                if j < nums[i]:
                    dp[j] = dp[j]
                else:
                    dp[j] = dp[j] or dp[j - nums[i]]
        return dp[j]

在完全背包问题中不用逆序,因为元素可以重复取,所以当前层左边的元素可以被覆盖。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值