LeetCode 494. 目标和

Posted 数据结构和算法

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode 494. 目标和相关的知识,希望对你有一定的参考价值。

截止到目前我已经写了 600多道算法题,其中部分已经整理成了pdf文档,目前总共有1000多页(并且还会不断的增加),大家可以免费下载
下载链接https://pan.baidu.com/s/1hjwK0ZeRxYGB8lIkbKuQgQ
提取码:6666

动态规划解决

这题之前讲过,具体可以看下566,DFS解目标和问题,由于当时时间仓促,只介绍了DFS的解决方式,其实这道题还有另外一种解决方式,就是使用动态规划来解决。


我们假设在一些数字前添加“+”,这些数字的和是plusSum。剩下的数字前添加“-”,这些数字的和是minusSum。我们要求的是
plusSum-minusSum=target ①
的方案数目。


假设数组中所有元素的和是sum。那么我们可以得到
plusSum+minusSum=sum ②


由公式和公式我们可以得到
minusSum*2=sum-target;


我们可以看到如果要让上面等式成立,sum-target必须是偶数

  • 也就是说如果sum-target不是偶数,无论怎么添加符号,表达式的值都不可能是target,直接返回0
  • 如果sum-target是偶数,我们只需要找出一些数字让他们的和等于minusSum,也就是(sum-target)/2的方案数。

通过上面的分析,这题就变成了从数组中选择一些元素,让他们的和等于(sum-target)/2的方案数。这和0-1背包非常像,具体可以看下371,背包问题系列之-基础背包问题


我们定义dp[i][j]表示从数组前 i 个元素中选取一些数字,让他们的和等于 j 的方案数。很明显我们最终只需要返回dp[length][(sum-target)/2]即可。


其中dp[0][0]=1,表示选择0个元素让他们的和等于0,只有一种方案。


遍历到当前数字num的时候,如果当前数字num大于j,那么我们是不能选择的,所以dp[i][j]=dp[i-1][j]。他表示的意思就是前 i个元素中不选择第 i个元素,而选择前 i个元素中其他的一些数字,让他们的和等于j的方案数。


如果当前数字num小于或等于j,我们可以选择也可以不选择。如果不选择就是dp[i][j]=dp[i-1][j],如果选择就是dp[i][j]=dp[i-1][j-num];那么总的方案数就是
dp[i][j]=dp[i-1][j]+dp[i-1][j-num]


递推公式如下

if (j >= num) {//不选num和选num
    dp[i][j] = dp[i - 1][j] + dp[i - 1][j - num];
} else {//不能选择num
    dp[i][j] = dp[i - 1][j];
}

通过上面的分析,我们再来看下最终代码

public int findTargetSumWays(int[] nums, int target) {
    int length = nums.length;
    //求数组中所有数字的和
    int sum = 0;
    for (int num : nums)
        sum += num;

    //如果所有数字的和小于target,或者sum - target是奇数,
    //说明无论怎么添加符号,表达式的值都不可能是target
    if (sum < target || ((sum - target) & 1) != 0) {
        return 0;
    }
    //我们要找到一些元素让他们的和等于capacity的方案数即可。
    int capacity = (sum - target) >> 1;
    //dp[i][j]表示在数组nums的前i元素中选择一些元素,
    //使得选择的元素之和等于j的方案数
    int dp[][]= new int[length + 1][capacity + 1];
    //边界条件
    dp[0][0] = 1;
    for (int i = 1; i <= length; i++) {
        for (int j = 0; j <= capacity; j++) {
            //下面是地推公式
            if (j >= nums[i - 1]) {//不选第i个和选第i个元素
                dp[i][j] = dp[i - 1][j] + dp[i-1][j - nums[i - 1]];
            } else {//不能选择第i个元素
                dp[i][j] = dp[i - 1][j];
            }
        }
    }
    //从数组前length个(也就是全部)元素中选择一些元素,让他们的
    //和等于capacity的方案数。
    return dp[length][capacity];
}

时间复杂度:O(n*capacity),n是数组的长度。
空间复杂度:O(n*capacity),capacity是(sum-target)/2。


代码优化

我们看到上面二维数组计算的时候,当前那一行的值只和上一行的有关,所以我们可以改成一维数组,这里要注意嵌套中的第二个for循环要倒叙遍历。因为改成一维数组之后,数组后面的值要依赖前面的(改变之前的),如果从前往后遍历,前面的值被修改了,会导致后面的运行结果错误。如果倒叙,也就是先计算数组后面的值,因为前面的还没有计算,也就是还没有被修改,所以不会导致结果错误。来看下代码

public int findTargetSumWays(int[] nums, int target) {
    int length = nums.length;
    //求数组中所有数字的和
    int sum = 0;
    for (int num : nums)
        sum += num;

    //如果所有数字的和小于target,或者sum - target是奇数,
    //说明无论怎么添加符号,表达式的值都不可能是target
    if (sum < target || ((sum - target) & 1) != 0) {
        return 0;
    }
    //我们要找到一些元素让他们的和等于capacity的方案数即可。
    int capacity = (sum - target) >> 1;
    int dp[] = new int[capacity + 1];
    //边界条件
    dp[0] = 1;
    for (int i = 1; i <= length; i++) {
        //注意,这里要倒叙
        for (int j = capacity; j >= 0; j--) {
            /*
            地推公式
            if (j >= nums[i - 1]) {
                dp[j] = dp[j] + dp[j - nums[i - 1]];
            } else {
                dp[j] = dp[j];
            }
             */
            //上面的代码合并之后的
            if (j >= nums[i - 1]) {
                dp[j] += dp[j - nums[i - 1]];
            }
        }
    }
    return dp[capacity];
}

时间复杂度:O(n*capacity)。
空间复杂度:O(capacity)

以上是关于LeetCode 494. 目标和的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode 494. 目标和

LeetCode 494. 目标和

Leetcode刷题Python494. 目标和

LeetCode494. 目标和 / 474. 一和零 / 203. 移除链表元素 / 第 244 场力扣周赛

题目地址(494. 目标和)

494. 目标和