1.对应letecode链接:
2.题目描述:
3.解题方法:
1.这是这个经典的背包问题每个元素前面要么添加-号要么添加+号也就是每个位置有两种选择要么前面添加+ 要么添加-号。
2.我们可以定义递归函数进行尝试:[index......nums.size()]范围内进行尝试也就是[0...index]范围内每个位置有两种选择能否让target变为0如果能够让它变为0说明就有一种方法。具体请看代码
4.对应代码:
暴力递归
class Solution { public: int findTargetSumWays(vector<int>& nums, int target) { //递归含义为[0......]一直到数组的结尾 return process(nums, 0, target); } int process(vector<int>& nums, int index, int target) { if (index == nums.size()) { return target == 0 ? 1 : 0; } //每个位置有两种选择要么选择前面添加+号要么选择前面添加-号 return process(nums, index + 1, target + nums[index]) + process(nums, index + 1, target - nums[index]); } };
记忆化搜索
class Solution { public: unordered_map<int, unordered_map<int, int>>dp; int findTargetSumWays(vector<int>& nums, int target) { //递归含义为[0......]一直到数组的结尾 return process(nums, 0, target); } int process(vector<int>& nums, int index, int target) { if (index == nums.size()) { return target == 0 ? 1 : 0; } if (dp.count(index) && dp[index].count(target)) { //说明之前已经计算过了 return dp[index][target]; } //每个位置有两种选择要么选择前面添加+号要么选择前面添加-号 int ans = process(nums, index + 1, target + nums[index]) + process(nums, index + 1, target - nums[index]); dp[index][target] = ans; return ans; } };
可能有小伙伴说这也太水了吧?确实博主我自己都快受不了了,下面我们来看一下优化:
优化一:我们可以将数组种的数全部变成正数对答案不会有影响,因为反正每个数要么前面添加+号要么前面添加-号。
优化二:我们可以将数组种所有的数都进行累加如果target比sum要大一定是0种方法,如果target和sum的奇偶性不一样也一定是0种方法。这是应为你是这些数搞成来的如果你和sum的奇偶性不一样肯定是0种方法。
优化三:对于这个优化下面我们举个例子
下面我们假设数组为{1,2,3,4,5} ,target=3.下面我们将前面取+号的单独拿出来,将前面取负的单独拿出来。分别为P,Q。
P={1,3,5},Q={2,4}。那么一定就有P中所有数的累加和-Q中所有数的累加和=target.即P-Q=target.我们同时在等式两边加上P+Q。
P+P+Q-Q=target+P+Q -> 2*P=target+P+Q.而P+Q不就是刚好就是整个数组的累加和吗?也就是P=(sum+target)/2.从这里我们可以看出来一个P对应一个target。那么问题是不是就转换为在数组{1,2,3,4,5}中每个数有两种选择要么选要么不选能搞定P的数量。
对应优化代码:
class Solution { public: int findTargetSumWays(vector<int>& nums, int target) { int sum = 0; for (auto x : nums) { sum += x; } //如果目标比sum还要大或者两者的奇偶性不一样 return (target > sum) || (sum & 1) ^ (target & 1) ? 0 : Subset(nums, 0, (sum + target) / 2); } int Subset(vector<int>& nums, int index, int target) { if (index == nums.size()) { return target == 0 ? 1 : 0; } if (target < 0) { return 0; } //不选择当前位置的数 int ways = Subset(nums, index + 1, target); //选择当前位置的数 ways += Subset(nums, index + 1, target - nums[index]); return ways; } };
记忆化搜索:
class Solution { public: vector<vector<int>>dp; int findTargetSumWays(vector<int>& nums, int target) { for (int i = 0; i < nums.size(); i++) { nums[i] = nums[i] < 0 ? -nums[i] : nums[i]; } int sum = 0; for (auto x : nums) { sum += x; } if (target > sum || (sum - target) % 2 != 0) { return 0; } dp.resize(nums.size() + 1, vector<int>(sum + 1, -1)); //(target+sum)/2的最大范围为target==sum时也就是sum return Subset(nums, 0, (sum - target) / 2); } int Subset(vector<int>& nums, int index, int target) { if (index == nums.size()) { return target == 0 ? 1 : 0; } if (target < 0) { return 0; } if (dp[index][target] != -1) { return dp[index][target]; } //不选择当前位置的数 int ways = Subset(nums, index + 1, target); //选择当前位置的数 ways += Subset(nums, index + 1, target - nums[index]); dp[index][target] = ways; return ways; } };
严格位置依赖的动态规划:
class Solution { public: int findTargetSumWays(vector<int>& nums, int target) { for (int i = 0; i < nums.size(); i++) { nums[i] = nums[i] < 0 ? -nums[i] : nums[i]; } int sum = 0; for (auto x : nums) { sum += x; } if (target > sum || (sum & 1) ^ (target & 1)) { return 0; } return Subset(nums, 0, (sum - target) / 2); } int Subset(vector<int>& nums, int index, int target) { if (target < 0) { return 0; } int N = nums.size(); vector<vector<int>>dp(N + 1, vector<int>(target + 1)); dp[N][0] = 1; for (int i = N - 1; i >= 0; i--) { for (int j = 0; j <= target; j++) { dp[i][j] = dp[i + 1][j]; if (nums[i] <= j) { dp[i][j] += dp[i + 1][j - nums[i]]; } } } return dp[0][target]; } };