LeetCode 494
目标和
给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
示例:
输入: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。
提示:
- 数组非空,且长度不会超过 20 。
- 初始的数组的和不会超过 1000 。
- 保证返回的最终结果能被 32 位整数存下。
解法一:回溯法
解题思路:
通过回溯法尝试所有组合,最后得出结果
代码如下:
class Solution {
private int res=0;
public int findTargetSumWays(int[] nums, int S) {
return backtrack(nums,0,S);
}
public int backtrack(int[] nums, int depth, int S){
if(depth==nums.length)
{
if(S==0)
return 1;
return 0;
}
return backtrack(nums,depth+1,S+nums[depth])+
backtrack(nums,depth+1,S-nums[depth]);
}
}
时间复杂度:O(N^N)
空间复杂度:O(N)
:递归深度为N
解法一优化
上面我们在递归的时候其实进行了很多重复计算,比如说
现在我们递归回到第i个元素位置,此时要把它的符号从'+'
变成'-'
,然后继续往下递归,这时候,下面部分的计算其实是在选择'+'
的时候就计算过的,现在换成'-'
对下面部分其实是没有影响的,所以我们用一个HashMap
保存起来
class Solution {
HashMap<String,Integer> memo = new HashMap<>();
private int res=0;
public int findTargetSumWays(int[] nums, int S) {
return backtrack(nums,0,S);
}
public int backtrack(int[] nums, int depth, int S){
if(depth==nums.length)
{
if(S==0)
return 1;
return 0;
}
String key = depth+","+S;
if(memo.containsKey(key)) {
return memo.get(key);
}
int res = backtrack(nums,depth+1,S+nums[depth])+
backtrack(nums,depth+1,S-nums[depth]);
memo.put(key, res);
return res;
}
}
时间复杂度为O(2^N)
解法二:动态规划
解题思路:
涉及到子集划分的问题其实就是背包问题,类似上一道题最后一块石头的重量,我们将所有石头重量分为两份,然后用背包装上最大重量的石头,剩下的石头跟背包里面的石头重量相减就是最后一块石头的重量,这道题也类似
首先,如果我们把 nums
划分成两个子集 A
和 B
,分别代表分配 +
的数和分配 -
的数,那么他们和 S
存在如下关系:
sum(A) - sum(B) = target
sum(A) = target + sum(B)
sum(A) + sum(A) = target + sum(B) + sum(A)
2 * sum(A) = target + sum(nums)
综上,可以推出 sum(A) = (target + sum(nums)) / 2
,也就是把原问题转化成:nums
中存在几个子集 A,使得 A 中元素的和为(target + sum(nums)) / 2
?
我们设dp[i][j]=x
表示,若只在前 i
个物品中选择,若当前背包的容量为 j
,则最多有 x
种方法可以恰好装满背包。
因此我们可以得到动态规划转移方程如下:
dp[i][j] = dp[i-1][j]+dp[i][j-nums[i]]
也就是在拿到nums[i]
时,我们可以选择放进背包或者不放,放进背包就得从背包拿出等同重量的物品,将两种选择的结果加上就是当前dp[i][j]
的值
代码如下:
class Solution {
public int findTargetSumWays(int[] nums, int S) {
int num=0;
for(int n : nums)
num += n;
if(num<S || (num+S)%2==1)
return 0;
return backpack(nums, (num+S)/2);
}
public int backpack(int[] nums, int sum){
int[][] dp = new int[nums.length+1][sum+1];
for(int i=0; i<=nums.length; i++) {
dp[i][0]=1;
}
for(int i=1; i<=nums.length; i++){
for(int j=0; j<=sum; j++){
if(j>=nums[i-1]){ //装入背包
dp[i][j] = dp[i-1][j]+dp[i-1][j-nums[i-1]];
}
else { //装不下
dp[i][j] = dp[i-1][j];
}
}
}
return dp[nums.length][sum];
}
}
时间复杂度为O(N^2)
空间复杂度为O(N^2)
优化空间后
class Solution {
public int findTargetSumWays(int[] nums, int S) {
int num=0;
for(int n : nums)
num += n;
if(num<S || (num+S)%2==1)
return 0;
return backpack(nums, (num+S)/2);
}
public int backpack(int[] nums, int sum){
int[] dp = new int[sum+1];
dp[0]=1;
for(int i=1; i<=nums.length; i++){
for(int j=sum; j>=0; j--){
if(j>=nums[i-1]){
dp[j] = dp[j]+dp[j-nums[i-1]];
}
else {
dp[j] = dp[j];
}
}
}
return dp[sum];
}
}
解法来源
作者:labuladong
链接:https://leetcode-cn.com/problems/target-sum/solution/dong-tai-gui-hua-he-hui-su-suan-fa-dao-di-shui-shi/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。