目标和 动态规划
1. 题目描述
给定一个非负整数数组,a1, a2, ..., an,
和一个目标数S
。现在你有两个符号+
和-
。对于数组中的任意一个整数,你都可以从+
或-
中选择一个符号添加在前面。
返回可以使最终数组和为目标数S
的所有添加符号的方案数。
提示:
- 数组非空,且长度不会超过
20
。 - 初始的数组的和不会超过
1000
。 - 保证返回的最终结果能被
32
位整数存下。
示例:
输入:nums: [1, 1, 1, 1, 1], S: 3
输出:5
解释:
-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3
一共有5种方法让最终目标和为3。
2. 题解
这道题是一个常见的背包问题,我们可以用类似求解背包问题的方法来求出可能的方案数。
我们用dp[i][j]
表示用数组中的前i
个元素,组成和为j
的方案数。
public int findTargetSumWays(int[] nums, int S) {
int[][] dp = new int[nums.length][2001];
dp[0][nums[0] + 1000] = 1;
dp[0][-nums[0] + 1000] += 1;
for (int i = 1; i < nums.length; i++) {
for (int sum = -1000; sum <= 1000; sum++) {
if (dp[i - 1][sum + 1000] > 0) {
dp[i][sum + nums[i] + 1000] += dp[i - 1][sum + 1000];
dp[i][sum - nums[i] + 1000] += dp[i - 1][sum + 1000];
}
}
}
return S > 1000 ? 0 : dp[nums.length - 1][S + 1000];
}
注意到数组下标不能为负数,由于题目要求数组和不超过1000
,所以需要给dp[i][j]
的第二维预先增加1000
。
以nums: [2, 1, 1, 2], S: 0
为例,这里给dp[i][j]
的第二维预先增加6
,因为该数组和不超过6
。
public int findTargetSumWays(int[] nums, int S) {
int[][] dp = new int[nums.length][13];
dp[0][nums[0] + 6] = 1;
dp[0][-nums[0] + 6] += 1;
for (int i = 1; i < nums.length; i++) {
for (int sum = -6; sum <= 6; sum++) {
if (dp[i - 1][sum + 6] > 0) {
dp[i][sum + nums[i] + 6] += dp[i - 1][sum + 6];
dp[i][sum - nums[i] + 6] += dp[i - 1][sum + 6];
}
}
}
return S > 6 ? 0 : dp[nums.length - 1][S + 6];
}
dp[0][2 + 6]
表示用数组中的前0
个元素,组成和为8
的方案数。因为dp[i][j]
的第二维预先增加6
,所以实际上dp[0][2 + 6]
表示用数组中的前0
个元素,组成和为2
的方案数。
因此,这里dp[3][6]
表示用数组中的前3
个元素(包含4个元素),组成和为0
的方案数,即dp[3][0 + 6]
。
解释代码:
nums[0]
有+nums[0]
和-nums[0]
两种情况,接着遍历nums[1]
。
由于dp[0][-2 + 6] = 1 > 0
,所以dp[1][-2 + 1 + 6] += dp[0][-2 + 6]
,dp[1][-2 - 1 + 6] += dp[0][-2 + 6]
。
接着遍历nums[2]
,同理,dp[2][-3 + 1 + 6] += dp[1][-3 + 6]
,dp[2][-1 - 1 + 6] += dp[1][-1 + 6]
。
dp[2][-2 + 6]
表示用数组中的前2
个元素,组成和为-2
的方案数为2
(-2+1-1
和-2-1+1
)。
这里dp[2][-2 + 6]
先加dp[1][-3 + 6]
,再加dp[1][-1 + 6]
,所以要用+=
来累加方案数。
参考: