返回

算法必刷题系列[93]: 分割等和子集 (partition-equal-subset-sum)

前端


在算法必刷题系列中,我们已经学习了多种数据结构和算法。今天,我们来学习一道经典的 0-1 背包问题:分割等和子集

题目

给你一个只包含正整数的数组 nums。判断是否存在一个子集,使得子集和为目标和 target 的一半。

示例 1:

输入:nums = [1, 5, 11, 5], target = 11
输出:true
解释:我们可以选择 nums 中的子集 [1, 5, 5],和为 11,恰好等于目标和的一半。

示例 2:

输入:nums = [1, 2, 3, 5], target = 11
输出:false
解释:不存在 nums 中的子集和为 11 的一半。

提示:

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= 1000
  • 1 <= target <= 1000

解题思路

这道题可以用动态规划或回溯算法求解。

动态规划

状态定义:

dp[i][j] 表示考虑前 i 个元素,能否凑出和为 j 的子集

状态转移方程:

dp[i][j] = dp[i-1][j] || dp[i-1][j - nums[i]]

初始化:

dp[0][0] = true

具体步骤:

  1. 外层循环遍历所有元素,内层循环遍历所有可能的和。
  2. 如果不考虑当前元素,即 dp[i-1][j] 为 true,则 dp[i][j] 也为 true。
  3. 如果考虑当前元素,即 dp[i-1][j - nums[i]] 为 true,则 dp[i][j] 也为 true。
  4. 如果以上两个条件都不满足,则 dp[i][j] 为 false。
  5. 最后,判断 dp[n][target/2] 是否为 true 即可。

回溯算法

回溯函数:

def backtrack(i, cur_sum):
    if i == n:
        return cur_sum == target // 2
    # 考虑当前元素
    if backtrack(i + 1, cur_sum + nums[i]):
        return True
    # 不考虑当前元素
    if backtrack(i + 1, cur_sum):
        return True
    return False

具体步骤:

  1. 定义回溯函数 backtrack,其中 i 表示当前考虑的元素索引,cur_sum 表示当前子集的和。
  2. 如果 i 等于数组长度 n,表示已经考虑完所有元素,此时判断 cur_sum 是否等于目标和的一半即可。
  3. 如果考虑当前元素,则递归调用 backtrack 函数,并将 cur_sum 加上当前元素的值。
  4. 如果不考虑当前元素,则递归调用 backtrack 函数,保持 cur_sum 不变。
  5. 如果以上两个条件都不满足,则表示不存在符合条件的子集,返回 False。
  6. 最后,调用 backtrack(0, 0) 即可得到结果。

时间复杂度

  • 动态规划:O(n * target)
  • 回溯算法:O(2^n)

空间复杂度

  • 动态规划:O(n * target)
  • 回溯算法:O(n)

代码实现

动态规划

def canPartition(nums, target):
    n = len(nums)
    dp = [[False] * (target + 1) for _ in range(n + 1)]
    dp[0][0] = True
    for i in range(1, n + 1):
        for j in range(target + 1):
            dp[i][j] = dp[i-1][j]
            if j - nums[i] >= 0:
                dp[i][j] |= dp[i-1][j - nums[i]]
    return dp[n][target // 2]

回溯算法

def canPartition(nums, target):
    n = len(nums)
    def backtrack(i, cur_sum):
        if i == n:
            return cur_sum == target // 2
        # 考虑当前元素
        if backtrack(i + 1, cur_sum + nums[i]):
            return True
        # 不考虑当前元素
        if backtrack(i + 1, cur_sum):
            return True
        return False
    return backtrack(0, 0)