目标和

题目

给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 +-。对于数组中的任意一个整数,你都可以从 +-中选择一个符号添加在前面。

返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

示例 1:

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

注意:

  1. 数组的长度不会超过20,并且数组中的值全为正数。
  2. 初始的数组的和不会超过1000。
  3. 保证返回的最终结果为32位整数。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/target-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

暴力穷举

最初的思路就是暴力了,把所有情况都考虑一遍,只要数据量不大,都能搞定(笑,这里的数组长度不超过20,可以尝试一下。时间复杂度是 O ( 2 n ) O(2^n) O(2n),指数级时间复杂度。

class Solution {
public:
    
    vector<int> nums;
    int S;
    int sumWay = 0;
    
    int findTargetSumWays(vector<int>& nums, int S) {
        this->nums = nums;
        this->S = S;
        dfs(0, 0);
        return sumWay;
    }
    
    void dfs(int step, int sum) {
        if (step == nums.size() - 1) {
            if (sum + nums[step] == S) sumWay++;
            if (sum - nums[step] == S) sumWay++;
            return;
        }
        dfs(step + 1, sum + nums[step]);
        dfs(step + 1, sum - nums[step]);
    }
};

动态规划

考虑其他解法的话,这个求和问题乍一看就像背包问题,我们可以把它变为背包问题~

原问题可以理解为:把nums数组分为两个互补子集PN,使两个集合的差为S

sum(P) - sum(N) = S
sum(P) + sum(N) + sum(P) - sum(N) = sum(P) + sum(N) + S
2 * sum(P) = S + sum(nums)

经过如上推倒,题目转变为:从nums数组中找到子集P,其和等于target = (S + sum(nums)) / 2
这就转变为0/1背包问题了,可以使用动态规划解决。

dp[i]表示nums的所有子集中,和为i的子集个数。

初始条件

对于target0的情况,肯定有且只有一个情况,那就是所有的数都不取,即空集。

状态转移方程

d p [ i ] = d p [ i ] + d p [ i − n u m ] dp[i] = dp[i] + dp[i - num] dp[i]=dp[i]+dp[inum]
两个dp[i]我们称之为new dp[i]old dp[i],分别对应本次循环的num与下次循环的num

old dp[i],为不取本次循环num时和为i的子集的个数,dp[i-num]为取num时和为i的子集的个数,而new dp[i]则是没有取下次循环时num时和为i的子集的个数,相当于下次循环时的old dp[i]

class Solution {
public:
    
    int findTargetSumWays(vector<int>& nums, int S) {
        int ans = 0, sum = 0;
        for (int n : nums) sum += n;
        
        // 根据推到的方程,如果不是偶数,则一定没有结果
        if (sum < S || (sum + S) % 2 == 1) return 0;
        
        int target = (sum + S) >> 1;
        vector<int> dp(target + 1, 0);
        dp[0] = 1;  //有且有只有一种情况可以使和为0:所有值都不取
        
        // dp[i-n]为取n时和为target的解
        for (int n : nums) {
            for (int i = target; i >= n; i--) {
                dp[i] = dp[i] + dp[i - n];
            }
        }
        
        return dp[target];
    }
    
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值