leetcode 494.目标和
题目来源:https://leetcode-cn.com/problems/target-sum/
题目
给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
思路
本题是典型的“动态规划”问题。设数组的长度为n,
- 当数组的前n-1项(nums[0]到nums[n-2])按照题中的要求得出了S-nums[n-1],则最后“+nums[n-1]”就满足了题中要求,方法数加1。
- 或者数组的前n-1项(nums[0]到nums[n-2])按照题中的要求得出了S+nums[n-1],则最后“-nums[n-1]”也满足了题中要求,方法数加1。
设dp()
与findTargetSumWays()
的意义相同,vector<int> temp
为当前vector<int> nums
除最后一项外剩下的元素组成的数组、len
为nums
的长度。
所以可以得出“状态转移公式”:
d
p
(
n
u
m
s
,
S
)
=
d
p
(
t
e
m
p
,
S
−
n
u
m
s
[
l
e
n
−
1
]
)
+
d
p
(
t
e
m
p
,
S
+
n
u
m
s
[
l
e
n
−
1
]
)
dp(nums,S) = dp(temp,S-nums[len-1])+dp(temp, S+nums[len-1])
dp(nums,S)=dp(temp,S−nums[len−1])+dp(temp,S+nums[len−1])
普通的动态规划递归函数会超时,在这里不再说明。接下来用题中的“示例1”来说明使用“二维数组”来解题的方法。
表格中横坐标表示从前到后扫描数组,当前扫描到的数组元素。纵坐标表示的是此时的目标数。对应的表格内容则是对应的方法数。
如表格中的(nums[2] = 1,-1)= 3表示的是扫描到数组的第三个元素时,组合得到目标数S = -3的方法有3种。
从表格种能够得到(nums[4] = 1,3)= 5!
并且表格中-S和S对应的方法数是相同的,这样可以进一步缩短“二维数组”的规模。
构造数组:
- 初始化二维数组的所有元素都为“0”。把第一行中与数组第一个元素相等的列数对应的表格置为“1”。(这里要注意的是,原本也要标记-S的情况,由于上述的原因而省略了,但如果S = 0,则-S = S,对应的位置就要标记两次)
- 接下来从第一行开始循环,扫描每一个元素,扫描到 ( i , j ) (i,j) (i,j)是非零元素时,其 ( i + 1 , j ± n u m s [ i + 1 ] 的 绝 对 值 ) (i+1,j\pm nums[i+1]的绝对值) (i+1,j±nums[i+1]的绝对值)要加上 ( i , j ) (i,j) (i,j)对应的值。(扫描每一行之后别忘了把S=0对应的表格元素本身 × 2 \times2 ×2)
- 最后一行,S的绝对值列对应的表格元素就是本题要求的方法数
C++代码
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int S) {
// 如果目标数大于初始的数组的和则方法数为0
if (S > 1000) {
return 0;
}
int dp[20][1001];
int len = nums.size();
// 初始化dp数组
memset(dp, 0, sizeof(dp));
dp[0][nums[0]] = 1;
dp[0][0] *= 2;
for (int i = 0; i < len - 1; i++) {
for (int j = 0; j <= 1000; j++) {
if (dp[i][j]) {
// 如果j=0时只加一次即可
if (j) {
dp[i + 1][abs(j - nums[i + 1])] += dp[i][j];
}
dp[i + 1][j + nums[i + 1]] += dp[i][j];
}
}
// 每一行之后别忘了把S=0对应的表格元素本身*2
dp[i + 1][0] *= 2;
}
return dp[len - 1][abs(S)];
}
};
发现问题欢迎指出和纠正,谢谢!