自学内容网 自学内容网

Python面试宝典第50题:分割等和子集

题目

        给你一个只包含正整数的非空数组nums,请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

        示例 1:

输入:nums = [1, 5, 11, 5]
输出:True
解释:数组可以分割成[1, 5, 5]和[11]。

        示例 2:

输入:nums = [1, 2, 3, 5]
输出:False
解释:数组不能分割成两个元素和相等的子集。

备忘录法

        备忘录法的基本思想是:在递归过程中缓存已计算的结果,避免重复计算相同子问题。这样可以显著减少递归树的分支,提高算法效率。使用备忘录法求解本题的主要步骤如下。

        1、定义递归函数can_partition_helper。该函数接受四个参数:数组nums、目标和target、当前索引index、备忘录memo。其中,备忘录memo用于存储已计算过的子问题的结果。

        2、基本情况。

        (1)如果target为0,则表示找到了一个子集,返回True。

        (2)如果target小于0,或者index超出了数组范围,则表示无法找到合适的子集,返回False。

        (3)如果target和index的组合已经存在于备忘录memo中,则直接返回备忘录中的结果。

        3、进行递归。

        (1)对于每个元素nums[index],有两种选择:包含或不包含该元素。

        (2)如果包含nums[index],则递归调用can_partition_helper函数,计算剩余元素是否可以构成目标和target - nums[index]。

        (3)如果不包含nums[index],则递归调用can_partition_helper 函数,计算剩余元素是否可以构成目标和target。

        (4)如果任一递归路径返回True,则更新备忘录memo,并返回True。

        4、如果所有递归路径都返回False,则表示无法分割成两个子集,返回False。

        根据上面的算法步骤,我们可以得出下面的示例代码。

def partition_equal_subset_sum_by_memoization(nums):
    total_sum = sum(nums)
    # 如果总和不是偶数,则无法分割成两个子集
    if total_sum % 2 != 0:
        return False
    
    target = total_sum // 2
    memo = {}

    def can_partition_helper(nums, target, index):
        # 如果target为0,则表示找到了一个子集
        if target == 0:
            return True
        
        # 如果target小于0,或者index超出了数组范围,则表示无法找到合适的子集
        if target < 0 or index >= len(nums):
            return False
        
        # 如果target和index的组合已经存在于备忘录中,则直接返回备忘录中的结果
        if (target, index) in memo:
            return memo[(target, index)]

        # 包含nums[index]
        include = can_partition_helper(nums, target - nums[index], index + 1)
        # 不包含nums[index]
        exclude = can_partition_helper(nums, target, index + 1)

        # 更新备忘录memo
        memo[(target, index)] = include or exclude
        # 返回结果
        return include or exclude

    return can_partition_helper(nums, target, 0)

nums = [1, 5, 11, 5]
print(partition_equal_subset_sum_by_memoization(nums))

nums = [1, 2, 3, 5]
print(partition_equal_subset_sum_by_memoization(nums))

动态规划法

        使用动态规划法时,我们需要构建一个一维数组dp来存储达到每个子集和的可能性。数组dp的索引表示子集和,dp[i]表示能否通过选择数组中的元素达到和为i的子集。使用动态规划法求解本题的主要步骤如下。

        1、初始化。计算数组nums的总和total_sum,如果total_sum不是偶数,则无法分割成两个子集,直接返回False。否则,计算目标和target为total_sum // 2。

        2、构建DP数组。创建一个长度为target + 1的布尔型数组dp,其中dp[0]初始化为True,表示总和为0的子集始终存在,其他位置初始化为False。

        3、填充DP数组。对于每个元素num,从target到num的每个子集和i,更新dp[i]为dp[i]或dp[i - num]。

        4、返回dp[target],表示是否存在和为目标和target的子集。

        根据上面的算法步骤,我们可以得出下面的示例代码。

def partition_equal_subset_sum_by_dp(nums):
    total_sum = sum(nums)
    # 如果总和不是偶数,则无法分割成两个子集
    if total_sum % 2 != 0:
        return False
    
    target = total_sum // 2
    # 创建一个长度为target + 1的布尔型数组
    dp = [False] * (target + 1)
    dp[0] = True
    
    # 填充DP数组
    for num in nums:
        # 从target到num的每个子集和i
        for i in range(target, num - 1, -1):
            # 更新dp[i]
            dp[i] = dp[i] or dp[i - num]
    
    return dp[target]

nums = [1, 5, 11, 5]
print(partition_equal_subset_sum_by_dp(nums))

nums = [1, 2, 3, 5]
print(partition_equal_subset_sum_by_dp(nums))

总结

        使用备忘录法时,每个子问题只被计算一次,子问题的数量与数组nums的长度、目标和target成正比。总的时间复杂度为O(N * target),其中N是数组nums的长度,target是目标和的一半。备忘录的最大大小为N * target,故其空间复杂度为O(N * target)。相比纯递归法,备忘录法通过避免重复计算相同的子问题,大大提升了效率。


原文地址:https://blog.csdn.net/hope_wisdom/article/details/142318538

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!