LeetCode每日一题4.7

416. 分割等和子集

LeetCode每日一题4.7_第1张图片

问题分析

题目要求判断是否可以将一个只包含正整数的数组 nums 分割成两个子集,使得这两个子集的元素和相等。这是一个经典的 0-1 背包问题 的变种,可以通过动态规划(DP)来解决。

思路

总和为奇数时无法分割:
如果数组的总和是奇数,那么不可能将其分成两个和相等的子集,直接返回 False。
目标值:
如果总和是偶数,设总和为 total,则每个子集的和应该为 target = total // 2。
问题转化为:是否存在一个子集,其元素和等于 target。
动态规划思路:
定义一个布尔型 DP 数组 dp,其中 dp[j] 表示是否存在一个子集,其元素和等于 j。
初始化 dp[0] = True,表示和为 0 的子集总是存在(空集)。
遍历数组中的每个元素,更新 DP 数组:
对于当前元素 num,从大到小遍历 dp 数组,更新 dp[j] = dp[j] or dp[j - num]。
这样可以避免重复使用同一个元素。

代码

class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        # 计算数组总和
        total_sum = sum(nums)

        # 如果总和是奇数,无法分割成两个和相等的子集
        if total_sum % 2 != 0:
            return False

        # 目标值为总和的一半
        target = total_sum // 2

        # 初始化 DP 数组,dp[j] 表示是否存在子集和为 j
        dp = [False] * (target + 1)
        dp[0] = True  # 和为 0 的子集总是存在(空集)

        # 遍历数组中的每个元素
        for num in nums:
            # 从大到小遍历 DP 数组,避免重复使用同一个元素
            for j in range(target, num - 1, -1):
                dp[j] = dp[j] or dp[j - num]

        # 返回目标值是否可达
        return dp[target]  

复杂度分析

时间复杂度:
遍历数组中的每个元素,对于每个元素,需要遍历 dp 数组一次,因此总时间为 (O(n * target)),其中 (n) 是数组长度,(target) 是目标和。
空间复杂度:
使用了一个长度为 target + 1 的 DP 数组,因此空间复杂度为 (O(target))。

我的学习

0-1 背包问题,考虑 动态规划
两个子集的元素和相等,意味着:

把 nums 分成两个子集,每个子集的元素和恰好等于s /2,s 必须是偶数。
如果 s 是奇数, s /2不是整数,直接返回 false。

动态规划:

dp[j] = dp[j] or dp[j - num] 

dp[j] 的含义
dp[j] 表示是否存在一个子集,其元素和等于 j。
初始化时,dp[0] = True,表示和为 0 的子集总是存在(空集)。
2. dp[j - num] 的含义
dp[j - num] 表示是否存在一个子集,其元素和等于 j - num。
如果 dp[j - num] 为 True,说明存在一个子集,其元素和为 j - num。那么,加上当前元素 num 后,子集的总和变为 j。
3. dp[j] = dp[j] or dp[j - num] 的作用
这一行代码的作用是更新 dp[j]:
如果 dp[j] 已经为 True,说明之前已经找到了一个子集,其元素和为 j,此时无需更改。
如果 dp[j] 为 False,但 dp[j - num] 为 True,说明可以通过加入当前元素 num 来构造一个和为 j 的子集,因此将 dp[j] 设置为 True。
为什么要从大到小遍历:
在更新 dp[j] 时,必须确保每个元素只使用一次。如果从小到大遍历 dp 数组,可能会重复使用同一个元素。
例如,假设数组中有两个相同的元素 num,如果从小到大遍历,dp[j] 可能会被错误地更新为 True,因为同一个元素被多次使用。
因此,从大到小遍历可以避免重复使用同一个元素。

你可能感兴趣的:(我的学习记录,leetcode)