能否凑出和为总数一半的?

摘要:本题在线练习:LeetCode 416. 分割等和子集 - 在线练习(免费 · 无需登录 · AI 辅助)(https:onefly.topzero2Leetcodeplaygro
本题在线练习:LeetCode 416. 分割等和子集 - 在线练习(免费 · 无需登录 · AI 辅助)(https://onefly.top/zero2Leetcode/playground.html?id=416) 配套刷题网站 Zero2Leetcode - 内置本地 OJ + AI 教练,零门槛开刷 Hot 100。 题目概述 给定一个只包含正整数的数组 nums,判断是否能把它分成两个子集,使得两个子集的元素和相等。 核心思路:总和为偶数才有机会,目标是凑出 sum/2 设数组总和为 S。 如果 S 是奇数,不可能分成两个和相等的子集,直接返回 False 如果能分成两半,那么一定存在一个子集的和为 S/2 于是原题变成: 是否能从 nums 中选一些数,使得它们的和恰好等于 target = S/2? 这就是典型的 0/1 背包(每个数只能用一次),做“可达性”判断。 DP 定义与转移(1 维布尔背包) dp[t]:是否能凑出和为 t 初始化: dp[0] = True(什么都不选就能凑出 0) 对于每个数字 x,更新: dp[t] = dp[t] or dp[t - x] (t 从 target 递减到 x) 必须从大到小遍历 t,避免一个数字在同一轮被重复使用(否则就变成完全背包了)。 代码实现 from typing import List class Solution: def canPartition(self, nums: List[int]) -> bool: total = sum(nums) if total % 2 == 1: return False target = total // 2 dp = [False] * (target + 1) dp[0] = True for x in nums: for t in range(target, x - 1, -1): dp[t] = dp[t] or dp[t - x] if dp[target]: return True return dp[target] 逐行拆解 target = total // 2:只要能凑出一半,另一半自然也成立 dp[0] = True:空集合可达 0 外层枚举每个 x: 内层 t 从 target 递减: dp[t - x] 为真表示“之前能凑出 t-x”,那么加上当前 x 就能凑出 t 递减遍历保证 x 只用一次 提前返回:一旦 dp[target] 为真,答案就确定了 手动模拟 nums = [1, 5, 11, 5] 总和 S = 22,target = 11。 处理 1 后:可以凑出 1 处理 5 后:可以凑出 5, 6 处理 11 后:直接可以凑出 11(取单个 11) 因此返回 True。 复杂度分析 时间复杂度:O(n * target) 空间复杂度:O(target) 总结 这题的关键是把“分成两份相等”翻译成一个更具体的目标: 只要能凑出总和的一半,就能分割成功。 然后用 0/1 背包的布尔 DP,配合“从大到小遍历”这一条规则,就能稳定写对。