20210607 每日一题 目标和

题目

题目链接

代码

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {

    }
};

方法一:DFS 深度优先搜索

分析

题目中可以选择向数组中的每个整数前添加 + + + − - ,所以可以看作当前节点有且只有两条通向支路,只要统计所有结果等于 t a r g e t target target 的路径数量即可。

代码

class Solution {
private:
    int dfs(vector<int>& nums, int& target, int n, int cur) {
        if(n == nums.size()) return cur == target ? 1 : 0;
        int addition = dfs(nums, target, n + 1, cur + nums[n]);
        int subtraction = dfs(nums, target, n + 1, cur - nums[n]);
        return addition + subtraction;
    }
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        return dfs(nums, target, 0, 0);
    }
};

复杂度分析

  • 时间复杂度: O ( 2 n ) O(2^n) O(2n),其中 n n n 是数组 n u m s nums nums 的长度,算法需要遍历所有情况。
  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 n u m s nums nums 的长度。空间复杂度主要取决于递归调用的栈空间,栈的深度不超过 n n n

方法二:记忆化搜索

分析

在方法一的基础上,我们可以将 DFS 的函数中的可变参数 n n n c u r cur cur 记录下来作为记忆化容器的两个维度,返回值作为记忆化容器的记录值,以此来减少重复计算过程。

代码


class Solution {
private:
    unordered_map<string, int> cache;
    int dfs(vector<int>& nums, int& target, int n, int cur) {
        string key = to_string(n) + '_' + to_string(cur);
        if(cache.count(key)) return cache[key];
        if(n == nums.size()) {
            cur == target ? cache[key] += 1 : cache[key] += 0;
            return cache[key];
        }
        int addition = dfs(nums, target, n + 1, cur + nums[n]);
        int subtraction = dfs(nums, target, n + 1, cur - nums[n]);
        cache[key] = addition + subtraction;
        return cache[key];
    }
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        return dfs(nums, target, 0, 0);
    }
};

复杂度分析

  • 时间复杂度: O ( 2 n ) O(2^n) O(2n),其中 n n n 是数组 n u m s nums nums 的长度,算法需要遍历所有情况。
  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 n u m s nums nums 的长度。空间复杂度主要取决于递归调用的栈空间,栈的深度不超过 n n n

方法三:二维动态规划

分析

假设数组的元素和为 s u m sum sum,添加 + + + 号的元素之和为 a a a,添加 − - 号的元素之和为 b b b,得到的表达式的结果为: a + b = s u m a − b = t a r g e t \begin{aligned} & a+b=sum \\ & a-b=target \end{aligned} a+b=sumab=target 因此我们可以得到:
b = s u m − t a r g e t 2 ,   b = 2 n ,   n ∈ N b=\dfrac{sum-target}{2},\ b = 2n,\ n \in N b=2sumtarget, b=2n, nN b b b自然偶数,因此可以将问题转化成能在数组中抽取元素组成和为 b b b 的子集的个数。设定二维数组 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示在前 i i i 个元素中,组成和为 j j j 的子集的个数。
当没有任何元素可以选取时,元素和只能为 0 0 0,对应的方案数是 1 1 1, 因此在二维数组 d p [ i ] [ j ] dp[i][j] dp[i][j] 中, d p [ 0 ] [ 0 ] dp[0][0] dp[0][0] 值为 1 1 1,其余起始值均为 0 0 0。对于第 i i i 个元素有选和不选两种情况:

  • 如果 j ≥ n u m s [ i ] j \geq nums[i] jnums[i] 时,如果不选 n u m s [ i ] nums[i] nums[i],子集数是 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j];如果选 n u m s [ i ] nums[i] nums[i],子集数是 d p [ i − 1 ] [ j − n u m s [ i ] ] dp[i-1][j-nums[i]] dp[i1][jnums[i]],因此总方案数为: d p [ i − 1 ] [ j ] + d p [ i − 1 ] [ j − n u m s [ i ] ] dp[i-1][j]+dp[i-1][j-nums[i]] dp[i1][j]+dp[i1][jnums[i]]
  • 如果 j < n u m s [ i ] j < nums[i] j<nums[i] 时,则不能选 n u m s [ i ] nums[i] nums[i],子集数是 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j]

综上所述 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k] 的状态转移方程可表示为:
d p [ i ] [ j ] = { d p [ i − 1 ] [ j ] + d p [ i − 1 ] [ j − n u m s [ i ] ] i f   j ≥ n u m s [ i ] d p [ i − 1 ] [ j ] i f   j < n u m s [ i ] dp[i][j] = \begin{cases} dp[i-1][j]+dp[i-1][j-nums[i]] &if\ j \geq nums[i]\\ dp[i-1][j] &if\ j < nums[i] \end{cases} dp[i][j]={dp[i1][j]+dp[i1][jnums[i]]dp[i1][j]if jnums[i]if j<nums[i] d p [ n ] [ b ] dp[n][b] dp[n][b] 即为问题答案。

代码

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum = accumulate(nums.begin(), nums.end(), 0);
        if (sum - target < 0 || (sum - target) % 2 != 0) return 0;
        int b = (sum - target) / 2, len = nums.size();
        vector<vector<int>> dp (len + 1, vector<int>(b + 1, 0));
        dp[0][0] = 1;
        for(int i = 1; i <= len; ++i) {
            for(int j = 0; j <= b; ++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[len][b];
    }
};

复杂度分析

  • 时间复杂度: O ( n × b ) O(n \times b) O(n×b),其中 n n n 是数组长度, b b b 是负值元素和。
  • 空间复杂度: O ( n × b ) O(n \times b) O(n×b),其中 n n n 是数组长度, b b b 是负值元素和,实现动态规划需要创建 n × b n \times b n×b 的二维数组 d p dp dp

方法四:一维动态规划

分析

根据方法三的状态转移方程可知,更新 d p [ i ] [ ] dp[i][] dp[i][] 的每个元素值时,只依赖于 d p [ i − 1 ] [ ] dp[i-1][] dp[i1][] 的元素值。因此,可以使用一维滚动数组 d p [ j ] dp[j] dp[j] 来替代二维数组 d p [ i ] [ j ] dp[i][j] dp[i][j] 对方法三进行空间上的优化。 d p [ j ] dp[j] dp[j] 的状态转移方程可表示为:
d p [ j ] = d p [ j ] + d p [ j − n u m s [ i ] ] dp[j] = dp[j]+dp[j-nums[i]] dp[j]=dp[j]+dp[jnums[i]] d p [ b ] dp[b] dp[b] 即为问题答案。

要点

根据方法三的状态转移方程,实现时内层循环需采用倒序遍历的方式更新元素值。

代码

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum = accumulate(nums.begin(), nums.end(), 0);
        if (sum - target < 0 || (sum - target) % 2 != 0) return 0;
        int b = (sum - target) / 2;
        vector<int> dp (b + 1, 0);
        dp[0] = 1;
        for(auto num : nums) {
            for(int j = b; j >= 0; --j) {
                if(j >= num) {
                    dp[j] = dp[j] + dp[j - num];
                }
            }
        }
        return dp[b];
    }
};

复杂度分析

  • 时间复杂度: O ( n × b ) O(n \times b) O(n×b),其中 n n n 是数组长度, b b b 是负值元素和。
  • 空间复杂度: O ( b ) O(b) O(b),其中 b b b 是负值元素和,实现动态规划需要创建 b b b 的一维滚动数组 d p dp dp

拓展

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值