持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第12天,点击查看活动详情
题目:LeetCode
给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1" 。 返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目 示例 1:
输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 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
+1 + 1 + 1 + 1 - 1 = 3
示例 2:
输入: nums = [1], target = 1
输出: 1
提示:
- 1 <= nums.length <= 20
- 0 <= nums[i] <= 1000
- 0 <= sum(nums[i]) <= 1000
- -1000 <= target <= 1000
解题思路
- 分析题目可以转换成01背包问题:把 - 当成 0,不选;把 + 当成 1,选。
- 原本(-nums) + (+nums) == target,表达式左边和右边都加上 sum,就转换成 0 + 2 * (+nums) == sum + target,为方便,可以再进行除2操作,变成 +nums == (sum + target) / 2
- 由此可推出,如果(sum + target) 为奇数,说明不存在对应的 +nums 序列,即不可取
- 接下来就是正常的 01背包 动态规划问题
- 滚动数组:逆序,现在没有减法的情况下,可以保证无后效性
代码实现
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for(int temp : nums) {
sum += temp;
}
// 不在范围的情况 && 奇数无法匹配到选取方式(可证)
if(sum < target || (sum + target) % 2 == 1) {
return 0;
}
// 转换成 01背包:-号转成不取;+号转成取,两倍
// 实际上,只要考虑到 target + sum 即可,后面的和结果无关
int top = (sum + target) / 2 + 1;
// dp[i][j]:下标[0 ~ i]构成的数集,能得到 j - sum 的情况种数
int[] dp = new int[top];
// 初始化:第一个元素不取,只能给 0 带来 1 个种数
dp[0] = 1;
// 状态转移
for(int i = 0; i < nums.length; i++) {
for(int j = top - 1; j >= nums[i]; j--) {
// Case 1:取第 i 个元素
dp[j] += dp[j - nums[i]];
// Case 2: 不取第 i 个元素(一维情况下相当于不用考虑)
}
}
return dp[top - 1];
}
运行结果
复杂度分析
- 时间复杂度:O(n^2)
- 空间复杂度:O(n)
在掘金(JUEJIN) 一起分享知识, Keep Learning!