LeetCode_494: 目标和

目录

链接

问题

题解

我的题解1: 递归

我的题解2: 递归 -- 优化

题解3: 动态规划

题解4: 动态规划 -- 空间复杂度优化


链接

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 位整数存下。

题解

我的题解1: 递归

思路:

可以使用递归,枚举出所有可能的情况。具体地,当我们处理到第 i 个数时,我们可以将它添加 + 或 -,递归地搜索这两种情况。当我们处理完所有的 N 个数时,我们计算出所有数的和,并判断是否等于 S。

/**
 * 递归
 * 思路: nums数组中的每个元素都存在两种可能性: + 或者 -
 */
public int findTargetSumWays_Recu(int[] nums, int S) {
    if(nums.length == 0) return 0;
    return findTargetSumWays_(nums,-1,0,S);
}

public int findTargetSumWays_(int[] nums,int index, int S, int targetS) {
    ++ index;
    /**
     * 当计算完所有的数据, 比较当前S值与目标S值是否相同;
     * 如果相同, 则证明该组合方式可用, 返回1, 否则该组合方法不可用, 返回0
     */
    if(index >= nums.length){
        if(targetS == S){
            return 1;
        }else{
            return 0;
        }
    }
    /**
     * 递归查询满足 S == target 所需要剩余的组合方式
     */
    return findTargetSumWays_(nums,index, S + nums[index],targetS)
            + findTargetSumWays_(nums,index, S - nums[index],targetS) ;
}


复杂度分析
       时间复杂度: O(2^N),其中 N 是数组 nums 的长度
       空间复杂度: O(N),为递归使用的栈空间大小

我的题解2: 递归 -- 优化

/**
 * 递归 -- 优化
 *  将剩余S的值进行进行计算( + 或者 - ), 直至数组nums中的所有数据均参加计算, 判断此时S是否为 0;
 */
public int findTargetSumWays_RecuBett(int[] nums, int S) {
    if(nums.length == 0) return 0;
    return findTargetSumWays_RecuBett(nums,-1,S);
}

public int findTargetSumWays_RecuBett(int[] nums,int index, int targetS) {
    ++ index;
    /**
     * 当计算完所有的数据, 比较当前S值是否为 0;
     */
    if(index >= nums.length){
        if(targetS == 0){
            return 1;
        }else{
            return 0;
        }
    }
    return findTargetSumWays_RecuBett(nums,index, targetS - nums[index])
            + findTargetSumWays_RecuBett(nums,index, targetS + nums[index]) ;
}

题解3: 动态规划

思路:

这道题也是一个常见的背包问题,可以用类似求解背包问题的方法来求出可能的方法数。
用 dp[i][j] 表示用数组中的前 i 个元素,组成和为 j 的方案数。考虑第 i 个数 nums[i],它可以被添加 + 或 -,因此状态转移方程如下:
      dp[ i ][ j ] = dp[ i - 1 ][ j - nums[ i ] ] + dp[ i - 1 ][ j + nums[ i ] ]
也可以写成递推的形式:
     dp[ i ][ j + nums[ i ] ] += dp[ i - 1 ][ j ]
     dp[ i ][ j - nums[ i ] ] += dp[ i - 1 ][ j ]

由于数组中所有数的和不超过 1000,那么 j 的最小值可以达到 -1000。在很多语言中,是不允许数组的下标为负数的,因此我们需要给 dp[i][j] 的第二维预先增加 1000,即:
    dp[ i ][ j + nums[ i ] + 1000] += dp[ i - 1][ j + 1000]
    dp[ i ][ j - nums[ i ] + 1000] += dp[ i - 1][ j + 1000]

/**
 * 动态规划
 *  状态定义:
 *     dp(i,j): 使用数组前i个元素(包括i)可计算和为j的最多方法数
 *  状态转移方程:
 *       dp[i][j] = dp[i - 1][j - nums[i]] + dp[i - 1][j + nums[i]]
 *     or
 *       dp[i][j + nums[i]] += dp[i - 1][j]
 *       dp[i][j - nums[i]] += dp[i - 1][j]
 * 由于数组中所有数的和不超过 1000,那么 j 的最小值可以达到 -1000。
 * 数组中是不允许下标为负数的,因此需要给 dp[i][j] 的第二维预先增加 1000,即:
 *     dp[i][j + nums[i] + 1000] += dp[i - 1][j + 1000]
 *     dp[i][j - nums[i] + 1000] += dp[i - 1][j + 1000]
 */
public int findTargetSumWays(int[] nums, int S) {
    int[][] dp = new int[nums.length][2001];
    /**
     * 初始值: 使用前0个元素计算和为 nums[0]的数量为 1
     * 如果nums[0] == 0, 那么 nums[0] + 1000 与 -nums[0] + 1000 的值一致, 所以需要+=
     */
    dp[0][nums[0] + 1000] = 1;
    dp[0][-nums[0] + 1000] += 1;

    for (int i = 1; i < nums.length; i++) {
        //使用nums数组的前i个数据, 可以组成sum的方法数 (根据题意给出的范围, 穷举所有的可能性)
        for (int sum = -1000; sum <= 1000; sum++) {
            /**
             * 如果目标和为sum的方法数不为 0,即可以利用nums数组前i个元素组成此sum的值
             */
            if (dp[i - 1][sum + 1000] > 0) {
                dp[i][sum + nums[i] + 1000] += dp[i - 1][sum + 1000];
                dp[i][sum - nums[i] + 1000] += dp[i - 1][sum + 1000];
            }
        }
    }
    return S > 1000 ? 0 : dp[nums.length - 1][S + 1000];
}

复杂度分析
       时间复杂度: O(N∗sum),其中 N 是数组 nums 的长度。
       空间复杂度: O(N∗sum)。

题解4: 动态规划 -- 空间复杂度优化

思路:

动态规划的状态转移方程中,dp[i][...] 只和 dp[i - 1][...] 有关,因此我们可以优化动态规划的空间复杂度,只需要使用两个一维数组即可

/**
 * 动态规划 -- 空间复杂度优化
 */
public int findTargetSumWays_Better(int[] nums, int S) {
    int[] dp = new int[2001];
    dp[nums[0] + 1000] = 1;
    dp[-nums[0] + 1000] += 1;
    for (int i = 1; i < nums.length; i++) {
        int[] next = new int[2001];
        for (int sum = -1000; sum <= 1000; sum++) {
            if (dp[sum + 1000] > 0) {
                next[sum + nums[i] + 1000] += dp[sum + 1000];
                next[sum - nums[i] + 1000] += dp[sum + 1000];
            }
        }
        dp = next;
    }
    return S > 1000 ? 0 : dp[S + 1000];
}

复杂度分析
       时间复杂度:O(N∗sum),其中 N 是数组 nums 的长度。
       空间复杂度:O(sum)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值