Leetcode/目标和
给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个表达式
返回可以通过上述方法构造的、运算结果等于target的不同表达式的数目
1. 回溯法(全遍历)
public:
int res = 0;
int findTargetSumWays(vector<int>& nums, int target) {
backtrack(nums,0,target);
return res;
}
void backtrack(vector<int>& nums,int i,int target){
if(i==nums.size()&&target==0){ res++; return;}
if(i==nums.size()) return;
backtrack(nums,i+1,target-nums[i]);
backtrack(nums,i+1,target+nums[i]);
}
};
反向递归
class Solution {
public:
int res = 0;
int findTargetSumWays(vector<int>& nums, int target) {
backtrack(nums,nums.size()-1,target);
return res;
}
void backtrack(vector<int>& nums,int i,int target){
if(i==-1&&target==0){ res++; return;}
if(i==-1) return;
backtrack(nums,i-1,target-nums[i]);
backtrack(nums,i-1,target+nums[i]);
}
};
2. 记忆化搜索
可以在方法一的基础上记录指定位置在当前目标值下的方法数
用备忘录的方法减少重复遍历,与青蛙跳跃那题一致,但性能提升不大
记忆化搜索
class Solution {
public:
vector<map<int,int>> memo;//备忘录存储对应下标,指定目标值的解法,减少重复遍历
int findTargetSumWays(vector<int>& nums, int target) {
memo.resize(nums.size());
return backtrack(nums,0,target);
}
int backtrack(vector<int>& nums, int i,int target){
if(i==nums.size()){
if(target==0) return 1;
return 0;
}
if(memo[i].count(target)) return memo[i][target];//这一句在判定之后,否则超出内存
int left = backtrack(nums,i+1,target+nums[i]);
int right = backtrack(nums,i+1,target-nums[i]);
return memo[i][target]=left+right;
}
};
3. 动态规划
原问题乍一看无法转换为动态规划问题,因为每位都必须选择正负,不能不做选择
而且各位的数值不一样,假如dp[i]表示为凑成总和i的方案数显然不满足题意,无法得到状态转移方程
如果用dp[k][i]表示前k位凑成总和i的方案数,所需的空间复杂度过于庞大,很多冗余无效数据,导致处理时间性能不如直接回溯全遍历
这里要先对数据进行处理,将问题进行转化
记数组元素和为sum,添加-号元素和为neg,容易得到neg=(sum-targe)/2
这样我们就可以把问题转换成选取元素使得和为neg,和选取有限硬币得到金额是一样的问题,其实就是01背包问题
dp[i][j]表示数组nums的前i个数中选取元素,使得这些元素之和等于j的方案数
dp[i][j]=dp[i-1][j]+dp[i-1][j-nums[i]]
边界条件dp[0][i]=0,dp[0][0]=1
二维动态规划
class Solution {
public:
int findTargetSumWays(vector<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 n = nums.size(), neg = diff / 2;
vector<vector<int>> dp(n + 1, vector<int>(neg + 1));
dp[0][0] = 1;
for (int i = 1; i <= n; i++)
for (int j = 0; j <= neg; j++) {
dp[i][j] = dp[i - 1][j];
if (j >= nums[i - 1])
dp[i][j] += dp[i - 1][j - nums[i - 1]];
}
return dp[n][neg];
}
};
一维优化
这里内循环必须从后往前,因为后面元素要用到上一行前面位置元素,从前往后会被覆盖掉class Solution {
public:
int findTargetSumWays(vector<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;
vector<int> dp(neg + 1);
dp[0] = 1;
for (int& num : nums) {
for (int j = neg; j >= num; j--) {
dp[j] += dp[j - num];
}
}
return dp[neg];
}
};