Open azl397985856 opened 3 years ago
https://leetcode.com/problems/target-sum/
neg
.neg
: neg
, the sum of all the numbers to put a positive sign is pos
pos + neg = sum
and pos - neg = target
, so neg = (sum - target) / 2neg
must be a positive integer, so sum - target must be a positive integer, and it must be a even number.Brute force
class Solution {
int res = 0;
public int findTargetSumWays(int[] nums, int target) {
helper(nums, target, 0);
return res;
}
private void helper(int[] nums, int target, int start){
if(start == nums.length){
if(target == 0){
res++;
}
return;
}
helper(nums, target - nums[start], start + 1);
helper(nums, target + nums[start], start + 1);
}
}
2D DP
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for(int num: nums){
sum += num;
}
int diff = sum - target;
if(diff < 0 || diff % 2 != 0){
return 0;
}
int neg = diff / 2;
int n = nums.length;
int[][] dp = new int[n + 1][neg + 1];
dp[0][0] = 1;
for(int i = 1; i <= n; i++){
int cur = nums[i - 1];
for(int j = 0; j <= neg; j++){
dp[i][j] = dp[i - 1][j]; // not pick up the cur
if(j >= cur){
dp[i][j] += dp[i - 1][j - cur]; // pick up the cur
}
}
}
return dp[n][neg];
}
}
1D DP
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for(int num: nums){
sum += num;
}
int diff = sum - target;
if(diff < 0 || diff % 2 != 0){
return 0;
}
int neg = diff / 2;
int n = nums.length;
int[] dp = new int[neg + 1];
dp[0] = 1;
for(int num: nums){
for(int j = neg; j >= num; j--){
dp[j] += dp[j - num]; // not pick up the cur
}
}
return dp[neg];
}
}
用 plus_sum, minus_sum 记录加号和减号数字的和, 那么我们可以得到
plus_sum + minus_sum = sum; plus_sum - minus_sum = target
plus_sum = (sum+target) / 2
所以想要找到一个 子数组, 当中的元素和为 (sum+target)/2
dp[i][j]
代表使用前 i 个元素, 能实现 j 的方法数目
base case
dp[0][0] = 1
动态转移
dp[i][j] = dp[i-1][j] 使用前 i 个元素实现的方法也可以仅使用前 i-1 个元素实现, 所以就是 前 i-1 个元素实现的方法数
如果 j - nums[i-1] ≥ 0, 那么代表有可能 可以使用 nums[i-1] 和 j-nums[i-1] 来实现数字 j
所以 dp[i][j] += dp[i-nums[i-1]][j-1]
return
dp[-1][-1] 返回使用所有元素能实现 目标元素 的方法数目
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
# plus_num + minus_num = sum & plus_num - minus_num = target
# so plus_num = (sum+target) / 2
t = (sum(nums) + target)
if t % 2 != 0 or t < 0:
return 0
t = int(t/2)
l = len(nums)
dp = [[0 for _ in range(t+1)] for _ in range(l+1)]
dp[0][0] = 1
for i in range(1,l+1):
for j in range(0, t+1):
dp[i][j] = dp[i-1][j]
if j-nums[i-1] >= 0:
dp[i][j] += dp[i-1][j-nums[i-1]]
return dp[-1][-1]
令 n 为元素个数, t 为 (sum+target)/2
时间复杂度: O(nt) 两次遍历的时间复杂度
空间复杂度: O(nt) dp 数组的空间复杂度, 可以优化成一维的 dp 数组
https://leetcode.com/problems/target-sum/
这道题有两种方法。一种是通过数学把题目转化为01背包问题。 还有就是通过bottom-up的把数字累加到结果中。因为数字可能全为正或者全为负。 所以加一个offset,值就是sum。dp[i] 表示和为i的所有的排列数。 对每一个数,遍历所有可能的和,范围从-sum到sum,然后取+或者取-,把它加到相应的位置。 这里每次需要申请一个新的数组,因为不然还没用到的上一轮的值会被本轮的结果覆盖。
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for (int n : nums) {
sum += n;
}
if (target < -sum || target > sum) return 0;
int[] dp = new int[2 * sum + 1];
int offset = sum;
dp[-nums[0] + offset] += 1;
dp[nums[0] + offset] += 1;
for (int i = 1; i < nums.length; i++) {
int[] next = new int[2 * sum + 1];
for (int j = -sum; j <= sum; j++) {
if (dp[j + offset] > 0) {
next[j + offset + nums[i]] += dp[j + offset];
next[j + offset - nums[i]] += dp[j + offset];
}
}
dp = next;
}
return dp[target + sum];
}
}
时间:O(n*sum)
空间:O(n)
【思路】首先把问题变成一个找子集的问题,也就是那些子集加起来的和可以成为 (sum+target)/2 然后再用背包问题解决 【复杂度】O(n*m) m = (sum+target)/2
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
int ans = 0;
for(int i =0 ;i < nums.length;i++){
sum+=nums[i];
}
int pos ;
if(sum<target ||(sum+target)%2==1) return 0;
pos = (sum + target)/2;
pos = Math.abs(pos);
int[] dp = new int[pos+1];
dp[0] =1;
for(int i =0; i<nums.length;i++){
for(int j = pos; j>=nums[i];j--){
dp[j] += dp[j - nums[i]];
}
}
return dp[pos];
}
}
Go Code:
func findTargetSumWays(nums []int, target int) int {
sum := 0
for _, num := range nums {
sum += num
}
// 边界检查
if sum < target || (target+sum)%2 == 1 || (target+sum)/2 < 0 {
return 0
}
sum = (target + sum) / 2
dp := make([]int, sum+1)
dp[0] = 1
for i := range nums {
for j := sum; j >= 0; j-- {
if j-nums[i] >= 0 {
dp[j] += dp[j-nums[i]]
}
}
}
return dp[sum]
}
复杂度分析
令 n 为数组长度。
var findTargetSumWays = function(nums, target) { let sum = 0; for (const num of nums) { sum += num; } const diff = sum - target; if (diff < 0 || diff % 2 !== 0) { return 0; } const neg = Math.floor(diff / 2); const dp = new Array(neg + 1).fill(0); dp[0] = 1; for (const num of nums) { for (let j = neg; j >= num; j--) { dp[j] += dp[j - num]; } } return dp[neg]; };
题目转换为
两个边界条件 (sum + target) 需要被2整除,因为nums中都是整数 sum < Math.abs(target)时无解
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for (int num : nums) sum += num;
if (sum < Math.abs(target)) return 0;
if ((sum + target) % 2 == 1) return 0;
target = (sum + target) / 2;
int[] dp = new int[target + 1];
dp[0] = 1;
for (int i = 0; i < nums.length; i++) {
for (int j = target; j >= nums[i]; j--) {
dp[j] += dp[j - nums[i]];
}
}
return dp[target];
}
}
https://leetcode-cn.com/problems/target-sum/
DFS,DP
# 二进制枚举
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
def dfs(idx, total):
if (idx, total) in dic:
return dic[(idx, total)]
if (idx == n):
if total == target:
return 1
else:
return 0
left = dfs(idx+1, total + nums[idx])
right = dfs(idx+1, total - nums[idx])
dic[(idx, total)] = left + right
return left + right
dic = dict()
n = len(nums)
return dfs(0, 0)
# 全量DP
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
total = sum(nums)
if target > total: return 0
n = len(nums)
# f[i][j] 代表考虑前 i 个数,当前计算结果为 j 的方案数,令 nums 下标从 1 开始。
# 那么 f[n][target]为最终答案,f[0][0] = 1 为初始条件:代表不考虑任何数,凑出计算结果为 0 的方案数为 1 种
dp = [[0 for _ in range(2*total+1)] for _ in range(n + 1)]
dp[0][0 + total] = 1
for i in range(1, n+1):
x = nums[i - 1]
for j in range(-total, total+1):
if((j - x) + total >= 0):
dp[i][j + total] += dp[i - 1][j - x + total]
if((j + x) + total <= 2 * total):
dp[i][j + total] += dp[i - 1][j + x + total]
# print(dp)
return dp[n][target + total]
'''
nums = [1,1,1,1,1]
target = 3
[
-5 -4 -3 -2 -1 0 1 2 3 4 5
0 [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
1 [0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0],
2 [0, 0, 0, 1, 0, 2, 0, 1, 0, 0, 0],
3 [0, 0, 1, 0, 3, 0, 3, 0, 1, 0, 0],
4 [0, 1, 0, 4, 0, 6, 0, 4, 0, 1, 0],
5 [1, 0, 5, 0, 10, 0, 10, 0, 5, 0, 1]
]
'''
# 优化DP二维
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
# 状态定义
# dp[i][j] 代表在数组的前i个数中取元素,使得元素之和为j的方案数
# 状态转移
# dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i - 1]] (j >= num)
# 设neg为负数部分
# (sum - neg) - neg = target
# neg = (sum - target) // 2
# 也可以设pos为正数部分
# pos - (sum - pos) = target
# pos = (target + sum) // 2
# 这里设neg
sum = 0
for num in nums: sum += num
diff = (sum - target)
if diff < 0 or diff % 2 != 0:
return 0
neg = diff // 2
n = len(nums)
dp = [[0 for _ in range(neg + 1)] for _ in range(n + 1)]
# 根据状态定义进行初始化
# dp[0][0]为从数组前0个元素中选取元素,使得元素之和为0的方案数
# 当没有元素可取的时候,元素和只能为0,所以方案数为1 即没得选也是一种方案
dp[0][0] = 1
for i in range(1, n + 1):
num = nums[i - 1]
for j in range(0, neg + 1):
dp[i][j] = dp[i - 1][j]
if j >= num:
dp[i][j] = dp[i - 1][j] + dp[i - 1][j - num]
# nums[1,1,1,1,1], neg = 1
# j 0 1
# [[1, 0] 0
# , [1, 1] 1
# , [1, 2] 2
# , [1, 3] 3
# , [1, 4] 4
# , [1, 5]] 5
# i
return dp[n][neg]
# 优化DP一维
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
sum = 0
for num in nums: sum += num
diff = (sum - target)
if diff < 0 or diff % 2 != 0:
return 0
neg = diff // 2
n = len(nums)
dp = [0 for _ in range(neg + 1)]
dp[0] = 1
for i in range(1, n + 1):
num = nums[i - 1]
for j in range(neg, -1, -1):
# dp[j] = dp[j]
if j >= num:
dp[j] = dp[j] + dp[j - num]
else:
break
return dp[neg]
class Solution {
public int findTargetSumWays(int[] nums, int target) {
return countTarget(nums, 0, 0, target);
}
private int countTarget(int[] nums, int pos, int sum, int target) {
if(nums.length == pos) return sum == target ? 1 : 0;
return countTarget(nums, pos + 1, sum - nums[pos], target)
+ countTarget(nums, pos + 1, sum + nums[pos], target);
}
}
O(sum * n)
O(n)
思路: maintain 2D dp array, store the number of possible ways to reach target
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for (int num : nums) {
sum += num;
}
if (target > sum || target < -sum) return 0;
int[][] dp = new int[nums.length+1][2*sum+1];
dp[0][sum] = 1;
for (int i = 1; i <= nums.length; i++) {
for (int j = 0; j <= 2*sum; j++) {
if (j + nums[i-1] <= 2*sum) {
dp[i][j] += dp[i-1][j+nums[i-1]];
}
if (j - nums[i-1] >= 0) {
dp[i][j] += dp[i-1][j-nums[i-1]];
}
}
}
return dp[nums.length][sum+target];
}
}
Time Complexity: O(nm), n is the length of nums, m is the sum of all elements Space Complexity: O(nm), n is the length of nums, m is the sum of all elements
动态规划。我们将所有取正号的数字的和叫做positive,所有取负号的数字的和叫做negative,所有数组的和sum = positive + negative,我们想要达到的target = positive - negative,此题转化为0-1背包问题,我们找到能够组成和为positive的不同子数组的数目即可。接下来求解positive,结合sum和target的公式,positive = (sum + target) / 2。注意特殊情况的判断,sum + target不能为奇数,同时sum要大于等于target的绝对值(positive >= 0 and negative >= 0)。
这里压缩状态,用一维数组dp[j]表示总和为j的不同子数组的个数,状态转移方程:dp[j] = dp[j] + dp[j - nums[i]]。注意这里要用倒序遍历j,因为我们需要未更新的dp[j - nums[i]]来进行计算。
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for (int num : nums) {
sum += num;
}
if (sum < Math.abs(target)) return 0;
// sum = positive + negative
// target = positive - negative
if ((sum + target) % 2 == 1) return 0;
int positive = (sum + target) / 2;
// convert to 0-1 Knapsack problem
int[] dp = new int[positive + 1];
dp[0] = 1;
for (int i = 0; i < nums.length; i++) {
for (int j = positive; j >= 0; j--) {
// use reverse order. If we use the normal order, dp[j - nums[i]] has already been updated
if (j >= nums[i]) dp[j] = dp[j] + dp[j - nums[i]];
}
}
return dp[positive];
}
}
复杂度分析
这个题和昨天的非常像,昨天的我倒是看出来要求和除二分出一半,今天的愣是没看出来
言归正传,显然有如下式子,
# pos - neg = target
# pos + neg = sum_nums
所以pos=(sum_nums + target)/2
此时,题目转变成了在nums里找若干个数,使其和为pos,问有多少种做法 ( 因为确定了pos后,neg也就确定了 )
这就和昨天的题目非常类似了,不过今天没法用状态压缩处理(因为要存解法)
dp[i][j]表示0到i之间能取任意个数和为j的取法数量
初始化dp[0][0]=1, dp[0][pos-nums[0]]=1 (j>=nums[i])
有如下状态转移关系
发现dp[i]仅与dp[i-1]有关,故可以把dp[i][j]二维数组转换成一维,滚动更新dp。注意此时需要从右到左循环更新(因为j-nums[i]在左侧)
故有如下状态转移关系
def findTargetSumWays(self, nums: List[int], target: int) -> int:
sum_nums = sum(nums)
# pos - neg = target
# pos + neg = sum_nums
pos = sum_nums + target
if pos < 0 or pos % 2 == 1:
return 0
else:
pos //= 2
n = len(nums)
dp = [1] + [0] * pos
for i in range(n):
for j in range(pos, nums[i] - 1, -1):
dp[j] += dp[j - nums[i]]
return dp[-1]
TC: O(n*(sum-target)) SC: O(sum-target)
思路: 这道题给了我们一个数组,和一个目标值,让给数组中每个数字加上正号或负号,然后求和要和目标值相等,求有多少种不同的情况
方法一、回溯法 从第一个数字,调用递归函数,在递归函数中,分别对目标值进行加上当前数字调用递归,和减去当前数字调用递归,这样会涵盖所有情况,并且当所有数字遍历完成后,若目标值为0了,则结果 res 自增1
优化: 记忆化递归, 使用数组mem记录中间值,减少重复计算
方法二、动态规划,使用二维数组dp,其中dp[i][j]表示到第i - 1个数字时和为j的情况总数
方法三、动态规划,压缩为一维的哈希表unordered_map<int, int> dp, dp[i] 表示和为i的情况数
复杂度分析:
代码(C++):
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int n = nums.size();
unordered_map<int, int> dp(n + 1);
dp[0] = 1;
for (auto num : nums) {
unordered_map<int, int> tmp;
for (auto d : dp) {
int sum = d.first;
int cnt = d.second;
tmp[sum - num] += cnt;
tmp[sum + num] += cnt;
}
dp = tmp;
}
return dp[target];
}
};
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int S) {
int res = 0;
helper(nums,S,0,res);
return res;
}
void helper(vector<int>& nums, long S, int indx, int& res) {
if(indx >= nums.size()) {
if(S == 0) {
res++;
}
return;
}
helper(nums, S - nums[indx], indx + 1, res);
helper(nums, S + nums[indx], indx + 1 ,res);
}
};
Top Down Dynamic Programming
class Solution {
public boolean canPartition(int[] nums) {
int totalSum = 0;
// find sum of all array elements
for (int num : nums) {
totalSum += num;
}
// if totalSum is odd, it cannot be partitioned into equal sum subset
if (totalSum % 2 != 0) return false;
int subSetSum = totalSum / 2;
int n = nums.length;
Boolean[][] memo = new Boolean[n + 1][subSetSum + 1];
return dfs(nums, n - 1, subSetSum, memo);
}
public boolean dfs(int[] nums, int n, int subSetSum, Boolean[][] memo) {
// Base Cases
if (subSetSum == 0)
return true;
if (n == 0 || subSetSum < 0)
return false;
// check if subSetSum for given n is already computed and stored in memo
if (memo[n][subSetSum] != null)
return memo[n][subSetSum];
boolean result = dfs(nums, n - 1, subSetSum - nums[n - 1], memo) ||
dfs(nums, n - 1, subSetSum, memo);
// store the result in memo
memo[n][subSetSum] = result;
return result;
}
}
空间复杂度: O(mn)
时间复杂度: O(mn)
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
const findTargetSumWays = function(nums, target) {
const n = nums.length;
const maxSum = nums.reduce((acc, num) => acc + num, 0);
if (maxSum < Math.abs(target)) return 0;
const range = maxSum << 1; // range is [-maxSum...maxSum]
// dp[i][j] = num of ways to reach sum j (offset by maxSum) with first i nums
const dp = Array.from({length: n}, () => Array(range + 1).fill(0));
dp[0][maxSum + nums[0]] += 1;
dp[0][maxSum - nums[0]] += 1;
for (let i = 1; i < n; i++) {
for (let sum = -maxSum; sum <= maxSum; sum++) {
const offset = sum + maxSum; // offset by maxSum
if (dp[i - 1][offset] > 0) {
dp[i][offset + nums[i]] += dp[i - 1][offset];
dp[i][offset - nums[i]] += dp[i - 1][offset];
}
}
}
return dp[n - 1][target + maxSum];
};
https://leetcode-cn.com/problems/target-sum/
add1 -neg1 = target #分别代表正数的和,负数的和
add1 + neg1 = sumnums
add1 = (target + summnums)/2 问题转变为在nums里找若干个数,使得其和为add1
#状态:dp[i][j]0-i的数字组合得到j的数目 d[0][0] = 1 dp[0][add1-nums[0]]=1 (add1 >=num[i])
状态转移:dp[i][j]= dp[i-1][j] + (if j > nums[i] dp[i-1][j-nums[i]])
第i行只依赖于i-1行 进一步进行状态压缩
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
t = sum(nums) + target
if t % 2 or t < 0:
return 0
t= t // 2
dp = [0] * (t+1)
dp[0] = 1
for n in nums:
for j in range(t,n-1,-1):
dp[j] += dp[j-n]
return dp[-1]
复杂度分析:
时间复杂度:(n*t)
空间复杂度:O( t+1)
DP
使用语言:Python3
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
# # Brute Force
# self.count = 0
# self.helper(nums, 0, 0, target)
# return self.count
# def helper(self, nums, i, curr_sum, t):
# if i == len(nums):
# if curr_sum == t:
# self.count += 1
# else:
# self.helper(nums, i+1, curr_sum + nums[i], t)
# self.helper(nums, i+1, curr_sum - nums[i], t)
# DP
total = sum(nums)
n_total = -total
if target > total:
return 0
dp = [[0] * (2 * total + 1) for _ in range(len(nums))]
dp[0][total + nums[0]] = 1
dp[0][total - nums[0]] += 1
for i in range(1, len(nums)):
for j in range(n_total, total + 1):
if dp[i - 1][j + total] > 0:
dp[i][j + nums[i] + total] += dp[i - 1][j + total]
dp[i][j - nums[i] + total] += dp[i - 1][j + total]
return dp[len(nums) - 1][target + total]
复杂度分析 时间复杂度:O(mn) 空间复杂度:O(mn)
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
memo={}
def dfs(start_index,current_target):
if start_index == len(nums)-1:
if nums[start_index] ==0 and current_target==0:
return 2
elif current_target== nums[start_index] or current_target==-nums[start_index]:
return 1
else:
return 0
total_expressions=0
next_target=current_target-nums[start_index]
if (start_index+1,next_target) in memo:
total_expressions+=memo[(start_index+1,next_target)]
else:
total_expressions+=dfs(start_index+1,next_target)
next_target=current_target+nums[start_index]
if (start_index+1,next_target) in memo:
total_expressions+=memo[(start_index+1,next_target)]
else:
total_expressions+=dfs(start_index+1,next_target)
memo[(start_index,current_target)]=total_expressions
return total_expressions
return dfs(0,target)
Time: O(n*t) t is the sum of target, n is the length of nums
Space: O(n*t)
public static int process1(int[] arr, int index, int rest) {
if(index == arr.length){
return rest==0?1:0;
}
return process1(arr,index+1,rest+arr[index])+process1(arr,index+1,rest-arr[index]);
}
class Solution:
def findTargetSumWays(self, nums: List[int], S: int) -> int:
# dp[][]
n = len(nums)
numS = sum(nums)
if numS == S and len(nums)== 1:
return 1
if numS == -S and len(nums)== 1:
return 1
if numS<S:
return 0
dp = [ [0 for _ in range(2*numS+1)] for _ in range(n) ]
dp[0][numS+nums[0]] = 1
dp[0][numS-nums[0]] += 1
for i in range(1,n):
for j in range(-numS -1, numS +1):
l = dp[i-1][ numS+j-nums[i]] if 0 <= numS+ j - nums[i] < 2* numS +1 else 0
r = dp[i-1][ numS+j+nums[i]] if 0 <= numS+ j + nums[i] < 2*numS +1 else 0
dp[i][numS+j] = l + r
return dp[-1][numS+S]
title: "Day 62 494. 目标和" date: 2021-11-10T16:57:32+08:00 tags: ["Leetcode", "c++", "NP"] categories: ["91-day-algorithm"] draft: true
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:
输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。
提示:
1 <= nums.length <= 200
1 <= nums[i] <= 100
- 1、np背包问题查看大佬的模板解法,仅需要进行一些改动即可解题,本质上属于同一类的题目
- 2、两种边界条件的情况可以直接排除,数组和小于目标值以及数组差值为奇数。
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int sum = accumulate(nums.begin(), nums.end(), 0);
if((sum - target) % 2 == 1 || sum < target) return false;
int n = (sum - target) / 2;
vector<int> dp(n + 1, 0);
dp[0] = 1;
for(int i : nums)
{
for (int j = n; j >= i; j--) {
dp[j] += dp[j - i];
}
}
return dp[n];
}
};
时间复杂度:O(n ^ 2)
空间复杂度:O(n)
将题目转换为是否能找到子序列使得其和为array的总和/2,成为0-1背包问题
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int sum = 0;
for (int x: nums) sum += x;
if (abs(target) > sum) return 0;
if (nums.size() == 1) {
return nums[0] == abs(target) ? 1 : 0;
}
if ((target + sum) % 2 == 1) return 0;
int positive = (target + sum) / 2;
vector<int> dp(positive + 1);
dp[0] = 1;
for (int i = 0; i < nums.size(); ++i) {
for (int j = positive; j >= nums[i]; --j) {
dp[j] += dp[j - nums[i]];
}
}
return dp.back();
}
};
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
if not nums:
return 0
dic = defaultdict(int)
if nums[0] != 0:
dic[nums[0]] = 1
dic[-nums[0]] = 1
else:
dic[nums[0]] = 2
for i in range(1, len(nums)):
tdic = defaultdict(int)
for d in dic:
tdic[d + nums[i]] = tdic[d + nums[i]] + dic[d]
tdic[d - nums[i]] = tdic[d - nums[i]] + dic[d]
dic = tdic
return dic[target]
DP. 赶due, 先打卡回来再补!
class Solution:
def findTargetSumWays(self, nums, target) -> bool:
t = sum(nums) + target
if t % 2:
return 0
t = t // 2
dp = [0] * (t + 1)
dp[0] = 1
for i in range(len(nums)):
for j in range(t, nums[i] - 1, -1):
dp[j] += dp[j - nums[i]]
return dp[-1]
const findTargetSumWays = (nums, target) => {
const sum = nums.reduce((a, b) => a + b);
if (target > sum) {
return 0;
}
if ((target + sum) % 2) {
return 0;
}
const halfSum = (target + sum) / 2;
nums.sort((a, b) => a - b);
let dp = new Array(halfSum + 1).fill(0);
dp[0] = 1;
for (let i = 0; i < nums.length; i++) {
for (let j = halfSum; j >= nums[i]; j--) {
dp[j] += dp[j - nums[i]];
}
}
return dp[halfSum];
};
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
dp = [defaultdict(int) for _ in range(len(nums) + 1)]
dp[0][0] = 1
for i, num in enumerate(nums):
for s, cnt in dp[i].items():
dp[i + 1][s + num] += cnt
dp[i + 1][s - num] += cnt
return dp[len(nums)][target]
class Solution(object):
def findTargetSumWays(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
t = sum(nums) + target
if t%2 != 0:
return 0
t = abs(t//2)
dp = [0] * (t+1)
dp[0] = 1
for i in range(len(nums)):
for j in range(t, nums[i]-1, -1):
dp[j] += dp[j-nums[i]]
return dp[-1]
时间复杂度:O(n*(target+total)/2) 空间复杂度:O((target+total)/2)
思路 DP
代码 使用语言:Python3
class Solution: def findTargetSumWays(self, nums: List[int], target: int) -> int:
# DP
total = sum(nums)
n_total = -total
if target > total:
return 0
dp = [[0] * (2 * total + 1) for _ in range(len(nums))]
dp[0][total + nums[0]] = 1
dp[0][total - nums[0]] += 1
for i in range(1, len(nums)):
for j in range(n_total, total + 1):
if dp[i - 1][j + total] > 0:
dp[i][j + nums[i] + total] += dp[i - 1][j + total]
dp[i][j - nums[i] + total] += dp[i - 1][j + total]
return dp[len(nums) - 1][target + total]
复杂度分析 时间复杂度:O(mn) 空间复杂度:O(mn)
动态规划,先操别人答案;
int findTargetSumWays(vector<int>& nums, int target) {
nums.insert(nums.begin(), 0);
const int N = nums.size();
int sum = 0;
for (int& x : nums)
sum += x; /* 题意: sum(nums[i]) <= 1000, 后面需要用作+offset确保dp数组的index >= 0 */
if (target > sum || target < -sum)
return 0;
auto dp = vector<vector<int>>(N, vector<int>(2 * sum + 1, 0));
dp[0][0 + sum] = 1; /* dp[i][S]: 用前i个数进行计算后得到和为S的方法的数量.
dp数组的第2维统一 +sum确保这一维的index >= 0
*/
for (int i = 1; i < N; i++) {
for (int s = -sum; s <= sum; s++) {
if (s + nums[i] + sum <= 2 * sum)
dp[i][s + nums[i] + sum] += dp[i - 1][s + sum];
if (s - nums[i] + sum >= 0)
dp[i][s - nums[i] + sum] += dp[i - 1][s + sum];
}
}
return dp[N - 1][target + sum];
}
class Solution {
public int findTargetSumWays(int[] nums, int target){
int len = nums.length;
int sum = 0;
for(int x : nums){
sum += x;
}
if(target > sum || target < -sum) return 0;
int[][] dp = new int[len+1][(sum << 2) + 1];
dp[0][sum] = 1;
for(int i = 1; i <= len;i++){
for(int j = 0 ; j <= sum << 2; j++){
if(dp[i-1][j] != 0){
dp[i][j - nums[i-1]] += dp[i - 1][j];
dp[i][j + nums[i-1]] += dp[i - 1][j];
}
}
}
return dp[len][sum + target];
}
}
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for (int i: nums) sum += i;
if (Math.abs(target) > sum) return 0;
int[][] dp = new int[nums.length][sum * 2 + 1];
dp[0][nums[0] + sum] = 1;
dp[0][sum - nums[0]] += 1;
for (int i = 1; i < nums.length; i++) {
for (int j = 0; j <= 2 * sum; j++) {
if (j - nums[i] < 0) {
dp[i][j] = dp[i-1][j+nums[i]];
} else if (j + nums[i] > 2 * sum) {
dp[i][j] = dp[i-1][j-nums[i]];
} else {
dp[i][j] = dp[i-1][j+nums[i]] + dp[i-1][j-nums[i]];
}
}
}
return dp[nums.length - 1][target + sum];
}
}
class Solution:
def findTargetSumWays(self, nums, target) -> bool:
t = sum(nums) + target
if t % 2:
return 0
t = t // 2
dp = [0] * (t + 1)
dp[0] = 1
for i in range(len(nums)):
for j in range(t, nums[i] - 1, -1):
dp[j] += dp[j - nums[i]]
return dp[-1]
class 目标和_494 {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0 , length = nums.length;
for(int i = 0; i < length; i ++) {
sum += nums[i];
}
// 正数和为n 则 n -( sum -n) = target => n = (sum + target) / 2 转化为求正数n 使得所有加上+号的数字 和为 (sum + target) / 2
if( sum < target || (sum + target) % 2 == 1) return 0;
target = Math.abs((sum + target)/ 2); //如果target为负值 转化为求正值 结果等价 那为什么不直接取加上-号的数字的和呢???? ( sum -n) - n = target => n = (sum -target)/ 2 后续重做用这个
int[][] dp = new int[length + 1][target + 1]; // 取+的数字个数可能为 0 ~ nums.length , 和数值取值范围为 0 ~ target
dp[0][0] = 1; // 1个都不取 和为 0 的种类数为1 其他dp[0][i] = 0 ; dp[i][0]在递推过程中求得
for(int i = 1; i<= length; i++) {
for(int j =0; j<= target; j ++ ) {
int x = nums[i - 1];
// 求和为j 每个数字有取或者不取两种
if(x <= j) dp[i][j] += dp[i - 1][j - x]; // 取nums[i]
dp[i][j] += dp[i - 1][j];// 不取nums[i]
// 如果简化为1维滚动 可以改成 倒序 if(x <= j) dp[j] += dp[j -x]
}
}
return dp[length][target];
}
}
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for (int num : nums) {
sum += num;
}
if ((target > sum) || ((target + sum) % 2 == 1)) {
return 0;
}
int positive = (target + sum) / 2;
int[] dp = new int[positive + 1];
dp[0] = 1;
for (int i = 0; i < nums.length; i++) {
for (int j = positive; j >= nums[i]; j--) {
dp[j] += dp[j - nums[i]];
}
}
return dp[positive];
}
}
Time: O(n (sum + target)/2) = O(n (sum + target))
Space: O((sum + target) / 2) = O(sum + target)
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for (int num : nums)
sum += num;
if (sum < Math.abs(target))
return 0;
if (((sum + target) & 1) == 1)
return 0;
sum = (sum + target) / 2;
int[] dp = new int[sum + 1];
dp[0] = 1;
for (int i = 0; i < nums.length; i++)
for (int j = sum; j >= nums[i]; j--)
dp[j] = dp[j] + dp[j - nums[i]];
return dp[sum];
}
time: O(negative * (total + target) / 2) space: O((total + target) / 2)
class Solution:
def solve(self, nums, target):
if (sum(nums) + target) % 2 == 1: return 0
t = (sum(nums) + target) // 2
dp = [[0] * (len(nums) + 1) for _ in range(t + 1)]
dp[0][0] = 1
for i in range(t + 1):
for j in range(1, len(nums) + 1):
dp[i][j] = dp[i][j-1]
if i - nums[j-1] >= 0: dp[i][j] += dp[i - nums[j-1]][j-1]
return dp[-1][-1]
转化成01背包问题,不过不是求最大价值,而是求重量为M有几种方式
状态转换方程为dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]]
此外,还有一些边界情况:
sum<abs(target),则结果不存在
sum+target为奇数,会导致最终和不是target,返回0
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum =0;
for(int num: nums){
sum+=num;
}
if(sum < Math.abs(target))return 0;
if(((sum + target) & 1)==1)return 0;
sum = (sum + target)/2;
///dp[i]只能通过dp[i-1]获得,所以可以压缩为1维,并且遍历j时要倒序
int[] dp = new int[sum+1];
dp[0]=1;
for(int i=0;i<nums.length;i++){
for(int j=sum;j>=nums[i];j--){
dp[j] = dp[j] + dp[j-nums[i]];
}
}
return dp[sum];
}
}
class Solution: def findTargetSumWays(self, nums: List[int], target: int) -> int: t = sum(nums) + target if t % 2: return 0 t = t // 2
dp = [0] * (t + 1)
dp[0] = 1
for i in range(len(nums)):
for j in range(t, nums[i] - 1, -1):
dp[j] += dp[j - nums[i]]
return dp[-1]
class Solution {
public int findTargetSumWays(int[] nums, int S) {
int sum = 0;
for(int num : nums) sum += num;
if(S > sum || (S + sum) % 2 == 1) return 0;
int target = (S + sum) / 2;
int[] dp = new int[target + 1];
dp[0] = 1;
for(int num : nums){
for(int j = target; j >= num; j--){
dp[j] = dp[j] + dp[j - num];
}
}
return dp[target];
}
}
转化为 01 背包问题,转化过程见下代码注释:
var findTargetSumWays = function(nums, target) {
// pos : nums的全正数子集 nag : nums 的全负数子集 其中有 pos + -nag = sum (nums正数和)
// pos + nag === target
// pos + posCounterpart + nag = target + posCounterPart
// obviously : pos + posCounterpart = sum
// sum + nag === target + posCounterPart
// we have : -nag = posCounterPart
// sum === target + 2posCounterPart
// posCounterPart = (sum - target) / 2
// 所以题目转换为 寻找等于 (sum - target) / 2 的 posCounterPart
const sum = nums.reduce((acc ,x) => acc + x , 0);
if( ((sum - target) & 1) === 1 || sum - target < 0) return 0;
let ans = 0;
const w = (sum - target) >> 1;
const dp = new Array(w + 1).fill(0);
dp[0] = 1;
for(let i = 0 ; i < nums.length ; ++i) {
for(let j = w ; j >= nums[i] ; --j){
dp[j] += dp[j - nums[i]];
}
}
return dp[w];
};
时间复杂度: o(n*(sum - target))
额外空间复杂度: o(sum - target)
附带 01 背包和完全背包的模板:
//01背包
for (let i = 0; i < n; i++) {
for (let j = m; j >= V[i]; j--) {
f[j] = Math.max(f[j], f[j-V[i]] + W[i]); // max / min / count
}
}
//完全背包
for (let i = 0; i < n; i++) {
for (let j = V[i]; j <= m; j++) {
f[j] = Math.max(f[j], f[j-V[i]] + W[i]); // max / min / count
}
}
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for (int num : nums){
sum += num;
}
int differ = sum - target;
// 如果target > sum 或 differ没法对半分
if (differ < 0 || differ % 2 != 0){
return 0;
}
int neg = differ / 2;
int[] dp = new int[neg + 1];
dp[0] = 1;
for (int num: nums){
for (int j = neg; j >= num; j--){
dp[j] += dp[j - num];
}
}
return dp[neg];
}
}
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int S) {
const int sum = std::accumulate(nums.begin(), nums.end(), 0);
if (sum < std::abs(S)) return 0;
int ans = 0;
dfs(nums, 0, S, ans);
return ans;
}
private:
void dfs(const vector<int>& nums, int d, int S, int& ans) {
if (d == nums.size()) {
if (S == 0) ++ans;
return;
}
dfs(nums, d + 1, S - nums[d], ans);
dfs(nums, d + 1, S + nums[d], ans);
}
};
class Solution {
int count = 0;
public int findTargetSumWays(int[] nums, int target) {
backtrack(nums, target, 0, 0);
return count;
}
public void backtrack(int[] nums, int target, int index, int sum) {
if (index == nums.length) {
if (sum == target) {
count++;
}
} else {
backtrack(nums, target, index + 1, sum + nums[index]);
backtrack(nums, target, index + 1, sum - nums[index]);
}
}
}
动态规划
``
class Solution {
private static final int TWO = 2;
/**
* 如果我们把 nums 划分成两个子集 A 和 B,分别代表分配 + 的数和分配 - 的数,那么他们和 target 存在如下关系:
* sum(A) - sum(B) = target
* sum(A) = target + sum(B)
* sum(A) + sum(A) = target + sum(B) + sum(A)
* 2 * sum(A) = target + sum(nums)
* 综上,可以推出 sum(A) = (target + sum(nums)) / 2,也就是把原问题转化成:nums 中存在几个子集 A,使得 A 中元素的和为 (target + sum(nums)) / 2?
*
*/
int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for (int n : nums) {
sum += n;
}
// 这两种情况,不可能存在合法的子集划分
if (sum < target || (sum + target) % TWO == 1 || (sum + target) < 0) {
return 0;
}
return subsets(nums, (sum + target) / TWO);
}
/**
* 计算 nums 中有几个子集的和为 sum
* dp[i][j] = x 表示,若只在前 i 个物品中选择,若当前背包的容量为 j,则最多有 x 种方法可以恰好装满背包
*
* @param nums nums数组
* @param sum 和
* @return 个数
*/
int subsets(int[] nums, int sum) {
int n = nums.length;
int[][] dp = new int[n + 1][sum + 1];
// base case
for (int i = 0; i <= n; i++) {
dp[i][0] = 1;
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= sum; j++) {
if (j >= nums[i-1]) {
// 两种选择的结果之和,第一个不装包,第二个装包
dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i-1]];
} else {
// 背包的空间不足,只能选择不装物品 i
dp[i][j] = dp[i-1][j];
}
}
}
return dp[n][sum];
}
}
时间复杂度:O(n * sum)
空间复杂度:O(n * sum)
int findTargetSumWays(vector<int>& nums, int target) {
int sum = 0;
for (int i = 0; i < nums.size(); ++i) {
sum += nums[i];
}
if (abs(target) > sum) {
return 0;
}
if ((target + sum) % 2 == 1) {
return 0;
}
int size = (target + sum) / 2;
vector<int> dp(size + 1, 0);
dp[0] = 1;
for (int i = 0; i < nums.size(); i++) {
for (int j = size; j >= nums[i]; j--) {
dp[j] += dp[j - nums[i]];
}
}
return dp[size];
}
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
#dp[i][j] num of ways sum to J using 0:i num
if (sum(nums)+target) %2 ==1:
return 0
t = (sum(nums)+target) //2
if t <0:
return 0
dp = [[0 for _ in range(len(nums)+1)] for _ in range(t+1)]
print(dp)
dp[0][0] = 1
for i in range(t+1):
for j in range(1,len(nums)+1):
dp[i][j] = dp[i][j-1]
if i >= nums[j-1]:
dp[i][j] += dp[i-nums[j-1]][j-1]
return dp[-1][-1]
int findTargetSumWays(vector
if (abs(target) > sum) {
return 0;
}
if ((target + sum) % 2 == 1) {
return 0;
}
int size = (target + sum) / 2;
vector<int> dp(size + 1, 0);
dp[0] = 1;
for (int i = 0; i < nums.size(); i++) {
for (int j = size; j >= nums[i]; j--) {
dp[j] += dp[j - nums[i]];
}
}
return dp[size];
}
思路:
定义状态 搞清楚需要输出的结果后,就可以来想办法画一个表格,也就是定义dp数组的含义。根据背包问题的经验,可以将dp[ i ][ j ]定义为从数组nums中 0 - i 的元素进行加减可以得到 j 的方法数量。
状态转移方程 搞清楚状态以后,我们就可以根据状态去考虑如何根据子问题的转移从而得到整体的解。这道题的关键不是nums[i]的选与不选,而是nums[i]是加还是减,那么我们就可以将方程定义为:
dp[ i ] [ j ] = dp[ i - 1 ] [ j - nums[ i ] ] + dp[ i - 1 ] [ j + nums[ i ] ] 可以理解为nums[i]这个元素我可以执行加,还可以执行减,那么我dp[i] [j]的结果值就是加/减之后对应位置的和。
参考链接:
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int n = nums.length;
int sum = 0;
for (int num : nums) {
sum += num;
}
//边界条件
if(target < -sum || target > sum){
return 0;
}
int t = 2 * sum + 1;
//分步达到结果 动态规划
int[][] dp = new int[n][t];
//初始化
if (nums[0] == 0) {
dp[0][sum] = 2; //索引只能从0开始,所以[-sum,sum]变成[0,2*sum] 0 -> sum
} else {
dp[0][sum + nums[0]] = 1;
dp[0][sum - nums[0]] = 1;
}
for (int i = 1; i < nums.length; i++) {
for (int j = 0; j < t; j++) {
//判断dp[i - 1][j - nums[i]]是否存在
if (j - nums[i] >= 0) {
dp[i][j] += dp[i - 1][j - nums[i]];
}
//判断dp[i - 1][j + nums[i]]是否存在
if (j + nums[i] < t) {
dp[i][j] += dp[i - 1][j + nums[i]];
}
}
}
return dp[n - 1][sum + target];
}
}
时间复杂度:O(N*SUM)
空间复杂度:O(N*SUM)
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
#dp[i][j] num of ways sum to J using 0:i num
if (sum(nums)+target) %2 ==1:
return 0
t = (sum(nums)+target) //2
if t <0:
return 0
dp = [[0 for _ in range(len(nums)+1)] for _ in range(t+1)]
print(dp)
dp[0][0] = 1
for i in range(t+1):
for j in range(1,len(nums)+1):
dp[i][j] = dp[i][j-1]
if i >= nums[j-1]:
dp[i][j] += dp[i-nums[j-1]][j-1]
return dp[-1][-1]
494. 目标和
入选理由
暂无
题目地址
https://leetcode-cn.com/problems/target-sum/
前置知识
题目描述
给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,target。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。
返回可以使最终数组和为目标数 target 的所有添加符号的方法数。