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. 目标和的主要内容,如果未能解决你的问题,请参考以下文章