494. 目标和 ●●

494. 目标和 ●●

描述

给你一个整数数组 nums 和一个整数 target 。

向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :

例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

示例

输入: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

题解

1. 回溯(暴力法)

数组 nums \textit{nums} nums 的每个元素都可以添加符号 + + + − - ,因此每个元素有 2 种添加符号的方法,nn 个数共有 2 n 2^n 2n 种添加符号的方法,对应 2 n 2^n 2n 种不同的表达式。当 n 个元素都添加符号之后,即得到一种表达式,如果表达式的结果等于目标数 target,则该表达式即为符合要求的表达式。

可以使用回溯的方法遍历所有的表达式,回溯过程中维护一个计数器 ans,当遇到一种表达式的结果等于目标数 target 时,将 ans 的值加 1。遍历完所有的表达式之后,即可得到结果等于目标数 ans 的表达式的数目。

  • 时间复杂度: O ( 2 n ) O(2^n) O(2n),其中 nn 是数组 nums 的长度。回溯需要遍历所有不同的表达式,共有 2 n 2^n 2n 种不同的表达式,每种表达式计算结果需要 O ( 1 ) O(1) O(1) 的时间,因此总时间复杂度是 O ( 2 n ) O(2^n) O(2n)
  • 空间复杂度: O ( n ) O(n) O(n),其中 n 是数组 nums 的长度。空间复杂度主要取决于递归调用的栈空间,栈的深度不超过 n。
class Solution {
public:

    void backtrack(int idx, vector<int>&nums, int target, int& ans){
        if(idx == nums.size() ){	
            if(target == 0) ++ans;	// 表达式完成且差值为0
            return;
        }
        //for(int i = idx; i < nums.size(); ++i){			// 逐个往下递归遍历即可,不需要for循环
            backtrack(idx+1, nums, target-nums[idx], ans);	// +nums[idx],target -= nums[idx]
            backtrack(idx+1, nums, target+nums[idx], ans);	// -nums[idx],target += nums[idx]
        //}
    }

    int findTargetSumWays(vector<int>& nums, int target) {
        int ans = 0;
        backtrack(0, nums, target, ans);
        return ans;
    }
};
2. 动态规划

元素前添加 + 或 -,则一定有 left组合 - right组合 = target;
left - (sum - left) = target;
left = (sum + target) / 2;
其中sum 和 target 已知且固定,因此存在唯一的和为 left 的组合。
此时问题就、转化为在集合nums中找出和为 left 的组合数量,且不可重复取值。

  • 二维数组
  1. dp[i][j] 表示:在数组 nums 的前 i 个数中选取元素,使得这些元素之和等于 j 的方案数。;
  2. j<nums[i-1]:不能选 nums[i-1],dp[i][j] = dp[i-1][j];
    j>nums[i-1]dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i-1]];
  3. dp[0][0] = 1;(装满容量为0的背包,有1种方法,就是装0件物品。)
  4. nums放在外循环,target在内循环。

时间复杂度: O ( n × ( sum − target ) ) O(n \times (\textit{sum}-\textit{target})) O(n×(sumtarget))
空间复杂度: O ( n × ( sum − target ) ) O(n \times (\textit{sum}-\textit{target})) O(n×(sumtarget))

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum = 0, n = nums.size();
        for(int num : nums) sum += num;
        if(sum + target < 0) return 0;
        if((sum + target) % 2 !=0) return 0;
        int positive = (sum + target) / 2;
        vector<vector<int>> dp(n+1, vector<int>(positive+1, 0));
        dp[0][0] = 1;
        for(int i = 1; i <= n; ++i){
            for(int j = 0; j <= positive; ++j){
                dp[i][j] = dp[i-1][j];                  // 选nums[i-1]
                if(j >= nums[i-1]){ 
                    dp[i][j] += dp[i-1][j-nums[i-1]];   // 不选nums[i-1]
                }
            }
        }
        return dp[n][positive];
    }
};
  • 一维滚动数组优化空间复杂度 O ( sum − target ) O( \textit{sum}-\textit{target}) O(sumtarget)

01背包 组合问题:装满容量为 left 的背包,有几种方法

  1. dp[j] 表示:填满 j 容积的包,有dp[j]种方法;
  2. dp[j] += dp[j - nums[i]];
    遍历到nums[i]时,凑成dp[j]就有dp[j - nums[i]] 种方法,最终的组合数量为所有dp[j - nums[i]]累加;
  3. dp[j] = 0; dp[0] = 1;(装满容量为0的背包,有1种方法,就是装0件物品。)
    从递归公式也可以看出,dp[j] (j>0)要保证是0的初始值,才能正确的由dp[j - nums[i]]推导出来。
  4. 滚动数组,nums放在外循环,target在内循环,且内循环倒序。

输入:nums: [1, 1, 1, 1, 1], target = 3

left = (target + sum) / 2 = (3 + 5) / 2 = 4

dp数组状态变化如下:
在这里插入图片描述

  • 时间复杂度:O(n × m),n为正数个数,m为背包容量
  • 空间复杂度:O(m),m为背包容量
class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum = 0;
        for(int num : nums) sum += num;
        if(abs(target) > sum) return 0;
        if((sum + target) % 2) return 0;
        int left = (sum + target) / 2;      // 目标组合和

        vector<int> dp(left+1, 0);
        dp[0] = 1;
        for(int i = 0; i < nums.size(); ++i){
            for(int j = left; j >= nums[i]; --j){   // 倒序遍历
                dp[j] += dp[j - nums[i]];   // 组合数量
            }              
        }
        return dp[left];
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值