# LeetCode 494、目标和
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
# 每个数字都有两种状态:被进行“+”, 或者被进行“-”
# 因此可以将数组分成 A 和 B 两个部分
# A 部分的数字全部进行“+”操作
# B 部分的数字全部进行“-”操作
# 设数组的和为 sum ,A 部分的和为 sumA ,B 部分的和为 sumB
# sumA + sumB = sum (1)
# 同时有: sumA - sumB = target (2)
# 将(1)式与(2)式相加,可以得到: 2sumA = sum + target (3)
# 即 sumA = (sum + target) / 2
# 所以,原问题转换为:
# 有一些物品,第 i 个物品的重量为 nums[i] , 背包的容量为 sumA ,问:有多少种方式将背包【恰好填满】
# 先去计算总和
total = sum(nums)
# 再去计算背包容量
bagSize = (target + total) // 2
# 题目有可能出现 target 是一个很小的负数
# 比如 nums = [ 100 ] ,target = -200
# 这个时候 bagSize 就是负数了,需要调整为正数
if bagSize < 0 : bagSize = -bagSize
# System.out.println(bagSize)
# 如果发现 target + sum 为奇数,则无解
# 比如 nums = [ 1 ] ,target = 2
# target + sum = 2 + 1 = 3 ,此时无解
# 因为 bagSize 必然是整数,即 (target + sum) / 2 必然是整数
# 那么只有 target + sum 为偶数才能得到整数
if (target + total) % 2 == 1 : return 0
# 在前 i 个数字中,凑出和为 j 的组合有多少种方法
# dp[0][0] 表示在前 0 个数字中,凑出和为 0 的组合,有多少种方法
dp = [[0] * (bagSize + 1) for _ in range(len(nums) + 1)]
# 初始化
for i in range(len(nums) + 1) :
# 在前 i 个数字中,凑出和为 0 的组合有多少种方法
# 答案是 1 种方法
# 即每次不选择第 i 个元素就行
dp[i][0] = 1
# 01 背包问题开始填充二维数组
for i in range( 1 , len(nums) + 1 ) :
for j in range(bagSize + 1) :
# 注意到 i 是从下标 1 开始访问的
# 1、背包容量小于当前元素
# 背包无法放入 nums[i - 1]
if j < nums[i - 1] :
dp[i][j] = dp[i - 1][j]
# 2、背包容量大于等于当前元素
# 背包可以放入 nums[i - 1]
else:
# 不选:方案数为 dp[i - 1][j]
# 选:方案数为 dp[i - 1][j - nums[i - 1]]
# 两者之和
dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i - 1]]
# 返回结果
return dp[len(nums)][bagSize]