题目
分析
这道题开始我是想用回溯,但一看数据量,肯定会超时(指数级的时间复杂度)。没有思路,想着应该是dp,怎么进行转化,转为我们熟悉的问题呢?题目的意思就是说将数组分成两堆n1,n2,使得 n1 - n2 = S 。且有n1 + n2 = sum。由这两个式子可得 n1 + n2 = 2 * n2 + S = sum。=》 n2 = (sum - S) / 2。这样我们可看出了也就是找到一个子集合,使得集合之和等于(sum - S) / 2的方法有多少种?这里我们可能还是会想回溯暴搜比如LeetCode 39组合之和,这里也可用dp来做。由于每个数只能用一次,这就转换为01背包问题。直接用二维dp熟悉了,直接用一维dp来做。dp[j] 表示容量为j 有多少种方案。怎么推导动态转移方程呢?还是仿照01背包问题的思想。就是用集合的思想,先去再加。
dp[j] += dp[j-nums[i]]——这也是求组合问题常用的转移方程。
注意:初始化时,dp【0】要为1,若为0,则dp数组全为0。这里1可以理解为容量0啥也不装,为1种方案。
代码
1 class Solution { 2 public: 3 int findTargetSumWays(vector<int>& nums, int S) { 4 int sum = 0; 5 for(auto s:nums){ 6 sum += s; 7 } 8 if(sum < S) return 0; //边界 9 int t = (sum - S); 10 if(t%2) return 0; //奇数没有方案 11 vector<int>dp(t/2+1,0); 12 dp[0] = 1;//初始化为1,容量为0时装0件物品,一种方案,若为0,则全部为0 13 for(int i = 0;i < nums.size();i++){ 14 for(int j = t / 2;j >= nums[i];j--){ 15 dp[j] += dp[j-nums[i]]; 16 } 17 } 18 return dp[t/2]; 19 } 20 };
时间复杂度O(n*m),n为背包个数,m为背包容量。
空间复杂度O(m)
回溯法代码
1 class Solution { 2 vector<vector<int>>res; 3 vector<int>path; 4 void backtracking(vector<int>nums,int target,int sum,int startIndex){ 5 if(sum == target){ 6 res.push_back(path); 7 } 8 for(int i = startIndex;i < nums.size() && nums[i] + sum <= target;i++){ 9 //做剪枝,如果nums[i] + sum > target直接终止 10 path.push_back(nums[i]); 11 sum += nums[i]; 12 backtracking(nums,target,sum,i+1); 13 sum -= nums[i]; 14 path.pop_back(); 15 } 16 } 17 18 public: 19 int findTargetSumWays(vector<int>& nums, int S) { 20 int sum = 0; 21 for(auto s:nums){ 22 sum += s; 23 } 24 if(sum < S) return 0; 25 int t = (sum - S); 26 if(t%2) return 0; //奇数没有方案 27 //用回溯来代替dp 28 sort(nums.begin(),nums.end()); //回溯中存在剪枝要先排序 29 backtracking(nums,t/2,0,0); 30 return res.size(); 31 } 32 };