[01背包]494.目标和
494. 目标和 - 力扣(LeetCode)
这个代码是用来解决“目标和”问题的,即在给定一个非负整数数组 nums
和一个整数 target
的情况下,通过在每个数字前添加 +
或 -
号,使得整个表达式的和等于 target
,求有多少种不同的方法。
方法思路
-
问题转换:首先,我们需要将问题转换为一个背包问题。假设所有添加
+
的数字之和为pos
,所有添加-
的数字之和为neg
。那么有:pos - neg = target
pos + neg = sum(nums)
通过这两个等式,可以推导出pos = (sum(nums) + target) / 2
。因此,问题转化为在nums
中找到若干数字,使其和为pos
,即一个典型的背包问题。
-
边界条件处理:
- 如果
sum(nums) < abs(target)
,那么无论如何组合都无法达到target
,直接返回 0。 - 如果
(sum(nums) + target)
不是偶数,那么pos
不是整数,同样无法达到,返回 0。
- 如果
-
动态规划(记忆化搜索):使用深度优先搜索(DFS)结合记忆化来避免重复计算。
dfs(i, c)
表示从前i
个数字中选出若干数字,使其和为c
的方法数。递归关系如下:- 如果当前数字
nums[i]
大于剩余容量c
,则只能不选该数字,即dfs(i-1, c)
。 - 否则,可以选择不选或选该数字,即
dfs(i-1, c) + dfs(i-1, c - nums[i])
。
- 如果当前数字
class Solution:def findTargetSumWays(self, nums: List[int], target: int) -> int:# p->+# q->-# p+q = s# p-q = target# p = (s-target)/2 # p必须是整数,s必须大于targets = sum(nums) - abs(target)if s<0 or s%2 == 1:return 0m = s//2memo = {}# i 是当前处理的数字在 nums 数组中的索引# c 是当前剩余的目标和(即背包问题中的剩余容量)def dfs(i,c):if(i,c) in memo:return memo[(i,c)]# 当 i 变为负数时,表示所有数字已经处理完毕。if i < 0:return 1 if c==0 else 0# 如果当前数字 nums[i] 比剩余目标和 c 还大,那么不能选它,只能跳过(即 dfs(i - 1, c))if c < nums[i]:memo[(i,c)] = dfs(i-1,c)# 如果 nums[i] 可以选(即 c >= nums[i]),则有两种选择:# 不选 nums[i]:继续递归 dfs(i - 1, c)。# 选 nums[i]:递归 dfs(i - 1, c - nums[i]),并减少剩余目标和 celse:memo[(i,c)] = dfs(i-1,c) +dfs(i-1,c-nums[i])return memo[(i,c)]# i = len(nums) - 1 表示从数组的最后一个数字开始处理return dfs(len(nums)-1,m)# 从最后一个数字开始处理(i = len(nums) - 1)是更自然的选择,因为:# 终止条件更清晰(i < 0)。# 更符合动态规划填表顺序(从后往前依赖)。# 记忆化存储更方便((i, c) 对更易管理)。# 与数学归纳法一致(假设 i 之后的问题已解决)。# 代码更简洁(减少边界判断)。