目标和

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 划分成两个子集 AB,分别代表分配 + 的数和分配 - 的数,那么他们和 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)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值