LeetCode 494. 目标和

题目描述

在这里插入图片描述

解决方法

方法一 暴力法

这道题目可以使用回溯法做,就是对数组中每个数前分别加上正号和负号进行遍历即可,时间复杂度为 O ( 2 n ) O(2^{n}) O(2n)

class Solution {
public:
    int count=0;
    int findTargetSumWays(vector<int>& nums, int target) {
        dfs(nums, 0, target);
        return count;
    }
    
    void dfs(vector<int>& nums, int index, int leftNum){
        int n = nums.size();
        if(index>=n){
            if(leftNum==0) count++;
            return;
        }
        dfs(nums, index+1, leftNum+nums[index]);
        dfs(nums, index+1, leftNum-nums[index]);
    }
};

方法二 动态规划

说实话,这个动态规划太取巧了,让我想起了高中时做数学题的感觉。要使用这个方法需要对题目进行分析和化归才行。
首先,我们先分析一下问题,问题中对每个数字只有两种操作,一种是加 +,一种是加 - ,所以,我们根据数组数字前的符号将数字分为两类,一类为正号数字和 p o s pos pos,一类为负号数字和 n e g neg neg(不包含数字前的符号,所以和为正),所以问题可以转化为:
p o s − n e g = t a r g e t pos-neg=target posneg=target
将数组中所有的元素求和,得到一个 s u m sum sum,于是有:
p o s + n e g = s u m pos+neg=sum pos+neg=sum
将这个式子带入到上一个公式中,可得
( s u m − n e g ) − n e g = t a r g e t (sum-neg)-neg=target (sumneg)neg=target
n e g = s u m − t a r g e t 2 neg = \frac{sum-target}{2} neg=2sumtarget
由于题干中数组中每个元素都是非负整数,所以 n e g ≥ 0 neg≥0 neg0,根据这个条件,我们可以在程序开始前先进行一次判断,如果整个数组元素和和 t a r g e t target target差值除以2不为非负整数,我们就可以直接返回0。
对于一个给定的数组, n e g neg neg是个常数值,我们的问题其实就转化成了从数组中寻找和为 n e g neg neg的子序列个数。
于是,我们定义一个数组 d p [ i ] [ j ] dp[i][j] dp[i][j],表示从前 i i i个数中找到和为 j j j的子序列和的个数。 i i i的取值为 [ 0 , n ] [0, n] [0,n] j j j的取值为 [ 0 , n e g ] [0, neg] [0,neg]
对于 i i i为0的情况,当 j = 0 j=0 j=0时, d p [ i ] [ j ] = 1 dp[i][j]=1 dp[i][j]=1,其余 d p [ i ] [ j ] = 0 dp[i][j]=0 dp[i][j]=0
中间的转移方程是这样的:
j < n u m s [ i ] j<nums[i] j<nums[i]时, d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j]=dp[i-1][j] dp[i][j]=dp[i1][j],当 j > = n u m s [ i ] j>=nums[i] j>=nums[i]时, d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i − 1 ] [ j − n u m s [ i ] ] dp[i][j]=dp[i-1][j]+dp[i-1][j-nums[i]] dp[i][j]=dp[i1][j]+dp[i1][jnums[i]]
这个可以这么理解,如果我要求 d p [ i ] [ j ] dp[i][j] dp[i][j],即我要求得前 i i i个数字中找到和为 j j j的子序列数目,可以拆分成为不包含第 i i i个数字的子序列数目( d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j]=dp[i-1][j] dp[i][j]=dp[i1][j]),以及包含第 i i i个数字的子序列数目( d p [ i − 1 ] [ j − n u m s [ i ] ] dp[i-1][j-nums[i]] dp[i1][jnums[i]])。
最后的代码如下:

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum = 0;
        for (int& num : nums) {
            sum += num;
        }
        int diff = sum - target;
        if (diff < 0 || diff % 2 != 0) {
            return 0;
        }
        int n = nums.size(), neg = diff / 2;
        vector<vector<int>> dp(n + 1, vector<int>(neg + 1));
        dp[0][0] = 1;
        for (int i = 1; i <= n; i++) {
            int num = nums[i - 1];
            for (int j = 0; j <= neg; j++) {
                dp[i][j] = dp[i - 1][j];
                if (j >= num) {
                    dp[i][j] += dp[i - 1][j - num];
                }
            }
        }
        return dp[n][neg];
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值