Leetcode494. 目标和
相似题目:
Leetcode416. 分割等和子集
题目:
给你一个整数数组
n
u
m
s
nums
nums 和一个整数
t
a
r
g
e
t
target
target 。
向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :
例如,
n
u
m
s
=
[
2
,
1
]
nums = [2, 1]
nums=[2,1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于
t
a
r
g
e
t
target
target 的不同 表达式 的数目。
示例 1:
输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 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
+1 + 1 + 1 + 1 - 1 = 3
示例 2:
输入:nums = [1], target = 1
输出:1
题解:
0-1背包问题
记数组的元素和为
sum
\textit{sum}
sum,添加
-
\texttt{-}
- 号的元素之和为
neg
\textit{neg}
neg,则其余添加
+
\texttt{+}
+ 的元素之和为
sum
−
neg
\textit{sum}-\textit{neg}
sum−neg,得到的表达式的结果为
(
sum
−
neg
)
−
neg
=
sum
−
2
⋅
neg
=
target
(
s
u
m
−
n
e
g
)
−
n
e
g
=
s
u
m
−
2
⋅
n
e
g
=
t
a
r
g
e
t
(\textit{sum}-\textit{neg})-\textit{neg}=\textit{sum}-2\cdot\textit{neg}=\textit{target} (sum−neg)−neg=sum−2⋅neg=target
(sum−neg)−neg=sum−2⋅neg=target(sum−neg)−neg=sum−2⋅neg=target
即
neg
=
sum
−
target
2
\textit{neg}=\dfrac{\textit{sum}-\textit{target}}{2}
neg=2sum−target
由于数组
nums
\textit{nums}
nums 中的元素都是非负整数,
neg
\textit{neg}
neg 也必须是非负整数,所以上式成立的前提是
sum
−
target
\textit{sum}-\textit{target}
sum−target是非负偶数。若不符合该条件可直接返回 0。
若上式成立,问题转化成在数组 nums \textit{nums} nums 中选取若干元素,使得这些元素之和等于 neg \textit{neg} neg,计算选取元素的方案数。我们可以使用动态规划的方法求解。
定义二维数组 dp \textit{dp} dp,其中 dp [ i ] [ j ] \textit{dp}[i][j] dp[i][j] 表示从数组的 [ 0 , i ] [ 0 , i ] [0,i]下标范围内选取若干个正整数,使得这些元素之和等于 j j j 的方案数。假设数组 nums \textit{nums} nums 的长度为 n,则最终答案为 dp [ n − 1 ] [ neg ] \textit{dp}[n-1][\textit{neg}] dp[n−1][neg]。
在定义状态之后,需要考虑边界情况。以下两种情况都属于边界情况。
- 如果不选取任何正整数,则被选取的正整数等于 0。因此对于所有 0 ≤ i < n 0≤i < n 0≤i<n,都有 d p [ i ] [ 0 ] = 1 dp [ i ] [ 0 ] = 1 dp[i][0]=1;
- 当 i = 0 i = 0 i=0 时,只有一个正整数 nums [ 0 ] \textit{nums}[0] nums[0]可以被选取,因此 d p [ 0 ] [ n u m s [ 0 ] ] = 1 dp [ 0 ] [ nums [ 0 ]] = 1 dp[0][nums[0]]=1;
- 当没有任何元素可以选取时, d p [ 0 ] [ j ] = 0 ( j > 0 ) dp[0][j]=0(j>0) dp[0][j]=0(j>0)。
当 1 ≤ i < n 1 \le i < n 1≤i<n时,对于数组 nums \textit{nums} nums中的第 i i i 个元素 num \textit{num} num(i 的计数从 1 开始),遍历 0 ≤ j ≤ neg 0 \le j \le \textit{neg} 0≤j≤neg,计算 dp [ i ] [ j ] \textit{dp}[i][j] dp[i][j] 的值:
如果 j < num j < \textit{num} j<num,则不能选 num \textit{num} num,此时有 dp [ i ] [ j ] = dp [ i − 1 ] [ j ] \textit{dp}[i][j] = \textit{dp}[i - 1][j] dp[i][j]=dp[i−1][j];
如果 j ≥ num j \ge \textit{num} j≥num,则如果不选 num \textit{num} num,方案数是 dp [ i − 1 ] [ j ] \textit{dp}[i - 1][j] dp[i−1][j],如果选 num \textit{num} num,方案数是 dp [ i − 1 ] [ j − num ] \textit{dp}[i - 1][j - \textit{num}] dp[i−1][j−num],此时有 dp [ i ] [ j ] = dp [ i − 1 ] [ j ] + dp [ i − 1 ] [ j − num ] \textit{dp}[i][j] = \textit{dp}[i - 1][j] + \textit{dp}[i - 1][j - \textit{num}] dp[i][j]=dp[i−1][j]+dp[i−1][j−num]。
因此状态转移方程如下:
dp [ i ] [ j ] = { dp [ i − 1 ] [ j ] , j < nums [ i ] dp [ i − 1 ] [ j ] + dp [ i − 1 ] [ j − nums [ i ] ] , j ≥ nums [ i ] \textit{dp}[i][j]=\begin{cases} \textit{dp}[i - 1][j], & j<\textit{nums}[i] \\ \textit{dp}[i - 1][j] + \textit{dp}[i - 1][j - \textit{nums}[i]], & j \ge \textit{nums}[i] \end{cases} dp[i][j]={dp[i−1][j],dp[i−1][j]+dp[i−1][j−nums[i]],j<nums[i]j≥nums[i]
最终得到 dp [ n − 1 ] [ neg ] \textit{dp}[n-1][\textit{neg}] dp[n−1][neg] 的值即为答案。
java代码:
/**
* @param nums
* @param target
* @return
*/
public static int findTargetSumWays(int[] nums, int target) {
int len = nums.length;
int sum = 0;
for (int i = 0; i < len; i++) {
sum += nums[i];
}
if ((sum - target) % 2 != 0) {
return 0;
}
int neg = (sum - target) / 2;
//dp[i][j] :在数组[0,i]中选取一些数,使得和为j
int[][] dp = new int[len][neg + 1];
//i=0时,只有一个正数nums[0]可以被选取
dp[0][nums[0]] = 1;
for (int i = 0; i < len; i++) {
//从[0,i]中不选任何正整数
dp[i][0] = 1;
}
for (int i = 1; i < len; i++) {
for (int j = 1; j <= neg; j++) {
if (j < nums[i]) {
dp[i][j] = dp[i - 1][j];
} else {
dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i]];
}
}
}
return dp[len - 1][neg];
}