问题:
给定一组非负整数nums,和一个目标数S,求给nums的各个元素添加符号后,使得和为S的可能性有多少?
Example 1: Input: nums is [1, 1, 1, 1, 1], S is 3. Output: 5 Explanation: -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 There are 5 ways to assign symbols to make the sum of nums be target 3. Constraints: The length of the given array is positive and will not exceed 20. The sum of elements in the given array will not exceed 1000. Your output answer is guaranteed to be fitted in a 32-bit integer.
解法:DP(动态规划) 0-1 knapsack problem(0-1背包问题)
首先,由于S的符号不确定,我们可以将问题转换为:
●将数组分为正数组和负数组,使得正数组的和为一个定值的可能性有多少种?
推导:
sum(P) - sum(N) = target
sum(P) + sum(N) + sum(P) - sum(N) = target + sum(P) + sum(N)
2 * sum(P) = target + sum(nums)
所以:sum(P) = (target + sum(nums)) / 2
因此,计算正数组和sum(P),由上式,首先排除奇数结果
if (target + sum(nums))%2==1 then return 0
由于本题数位限制,int可能不足->
if (target ^ sum(nums))&1==1 then return 0
另外,若给数组全部赋正号或全部赋负号,
S不能>sum(nums) , S不能<-sum(nums)
因此有:
if((newsum^S)&1 || S>newsum || S<-newsum) return 0;
接下来,对●问题的DP进行分析:
1.确定【状态】:
- 可选择的数:前 i 个数
- 和:s:0~newsum
2.确定【选择】:
- 选择当前的数nums[i]
- 不选择当前的数nums[i]
3. dp[i][s]的含义:
前 i 个数中,组成和=s 的可能性的个数。
4. 状态转移:
dp[i][s]= SUM {
- 选择 nums[i]:dp[i-1][s-nums[i]]:=前 i-1 个元素可组成和为 s-nums[i] 的可能性个数
- 不选择 nums[i]:dp[i-1][s]:=前 i-1 个元素可组成和为 s 的可能性个数
}
5. base case:
- dp[0][s]=false
- dp[i][0]=true
- dp[0][0]=true
♻️ 优化:去掉 i ,
将s倒序。
代码参考:
1 class Solution { 2 public: 3 //Eq1:Positive+Negative=sum 4 //Eq2:Positive-Negative=S 5 //Eq1+Eq2:2*Positive=sum+S 6 //Positive=(sum+S)/2 7 //to find S-> to find subset which sum=Positive 8 9 10 //dp[i][s]: in first i items, the number of ways whose sum is s 11 //case_1,choose i-th item: dp[i-1][s-val[i]] 12 //case_2,don't choose: dp[i-1][s] 13 //dp[i][s] = case_1 + case_2 14 //base case: dp[0][s] = 0 15 //dp[i][0] = 1 16 //dp[0][0] = 1 17 // 18 int findTargetSumWays(vector<int>& nums, int S) { 19 int newsum = 0; 20 for(int n:nums){ 21 newsum+=n; 22 } 23 if((newsum^S)&1 || S>newsum || S<-newsum) return 0; 24 newsum = (newsum+S)/2; 25 vector<int> dp(newsum+1, 0); 26 dp[0]=1; 27 for(int i=1; i<=nums.size(); i++) { 28 for(int s=newsum; s>=0; s--) { 29 if(s-nums[i-1]>=0) { 30 dp[s] += dp[s-nums[i-1]]; 31 } 32 } 33 } 34 return dp[newsum]; 35 } 36 };