图解题库 / 0-1 背包

494. 目标和

中等 含交互动画

学完这道你应该能: 说清它为什么用「0-1 背包」,而不是死记步骤; 合上页面,自己默写出核心代码; 用 30 秒把思路讲清楚。

题目描述

每个数前面放 +,使表达式结果等于 target,求方法数

nums = [1, 1, 1, 1, 1]
target = 3
输出 = 5

思路解析

记取正号的数之和为 P、取负的为 N。P+N=sum、P−N=target,解得 P=(sum+target)/2。问题变成:选一个子集,和恰好为 P,有几种选法——0-1 背包计数!这里 P=(5+3)/2=4。

建表 · dp[0]=1:dp[j] 表示选出子集和为 j 的方法数。dp[0]=1,空集凑出 0。目标看 dp[4]。

放入第 1 个 1:放入第一个 1,从大到小更新:dp[1] += dp[0] = 1。能凑出和 1 的方法有 1 种。

放入第 2 个 1:第二个 1:dp[2]=1、dp[1]=2。数值开始像杨辉三角一样长了。

放完 5 个 1:把 5 个 1 都放完,dp = [1,5,10,10,5],正好是杨辉三角第 5 行。看 dp[4] = 5

答案 = dp[P]:子集和为 P=4 的选法有 5 种,这就是答案。

遇到「给每个数选 +/− 凑 target」,一律转成「选子集和为 (sum+target)/2」的背包计数。一个数学变形就把难题打回经典模型。

参考代码(Python)

Python
1s = sum(nums)
2if (s + target) % 2 or s < abs(target): return 0   # 无解
3P = (s + target) // 2
4dp = [0] * (P + 1); dp[0] = 1
5for num in nums:
6    for j in range(P, num - 1, -1):    # 0-1 从大到小
7        dp[j] += dp[j - num]
8return dp[P]

复杂度分析

  • 时间复杂度:O(n × P) —— n 个数 × 目标和
  • 空间复杂度:O(P) —— 一维 dp

套路模板

记住骨架:dp[0]=1、内层从大到小、dp[j]+=dp[j−x]。关键往往是「先把题目变形成子集和」这一步。

Python
1dp = [0]*(W+1); dp[0] = 1            # 计数初值 1
2for x in items:
3    for j in range(W, x - 1, -1):    # 从大到小=每物一次
4        dp[j] += dp[j - x]           # 累加方案数
5return dp[W]

易错点

  • 不判 (sum+target) 的奇偶/范围 → ✅ 为奇数或越界直接返回 0(P 必须是非负整数才有解)
  • 内层从小到大 → ✅ 0-1 背包从大到小(每个数只能用一次)

以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握

下一题 →79. 单词搜索 ← 返回题库