-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy path0_count-of-subset-sum.js
177 lines (166 loc) · 4.99 KB
/
0_count-of-subset-sum.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
/**
*
* Problem:
* Given a set of positive numbers, find the total number of subsets whose sum is equal to
* a given number. Note: duplication is also counted (check Example 1).
*
* Example 1:
* Input: [1, 1, 2, 3], S=4
* Output: 3
* Explanation: The given set has '3' subsets whose sum is '4': [1, 1, 2], [1, 3], [1, 3]
* Note that we have two similar sets [1, 3], because we have two '1' in our input.
*
* Example 2:
* Input: [1, 2, 7, 1, 5], S=9
* Output: 3
* Explanation: The given set has '3' subsets whose sum is '9': [2, 7], [1, 7, 1],
* [1, 2, 1, 5]
*
*
* Note: this problem is very similar as 10_subsets/40_combination-sum-ii, but that problem
* we need to get all the actual combinations, not the count, so even the sub-problems
* are the same (the changing states are the same), but the actual combinations can
* be different, so that's why for that problem dynamic programming is not appropriate.
*
*/
/**
*
* Solution 1: recursive brute force.
*
* @param {number[]} nums
* @param {number} targetSum
* @return {number}
*/
function countOfSubsetSum1(nums, targetSum) {
return _recursive(nums, targetSum, 0);
}
/**
*
* Similar as the problem 416_partition-equal-subset-sum, the only difference here
* is we need to return the count, so if we have a match return 1, otherwise return
* 0, for one `currentIndex`, the final count would be the sum of the count when it's
* selected and the count of it's skipped.
*
* Time: O(2^n) <- each number can either be selected or skipped
* Space: O(nm) m: targetSum <- recursion stack
*
* @param {number[]} nums
* @param {number} targetSum
* @param {number} currentIndex
* @return {number}
*/
function _recursive(nums, targetSum, currentIndex) {
if (targetSum === 0) {
return 1;
}
if (currentIndex === nums.length || targetSum < 0) {
return 0;
}
const selectCurrentCount = _recursive(
nums,
targetSum - nums[currentIndex],
currentIndex + 1
);
const skipCurrentCount = _recursive(nums, targetSum, currentIndex + 1);
return selectCurrentCount + skipCurrentCount;
}
/**
*
* Solution 2: top-down dynamic programming with memoization.
*
* @param {number[]} nums
* @param {number} targetSum
* @return {number}
*/
function countOfSubsetSum2(nums, targetSum) {
const n = nums.length;
const memo = new Array(n).fill(0).map(() => new Array(targetSum + 1));
return _recursiveWithMemo(nums, targetSum, 0, memo);
}
/**
*
* The only changing variables are `currentIndex` and `targetSum`, so use two-dimension
* `memo` to prevent duplication sub-problems.
*
* Time: O(mn) m: `targetSum`
* Space: O(mn) <- for `memo` and recursion stack
*
* @param {number[]} nums
* @param {number} targetSum
* @param {number} currentIndex
* @param {number[][]} memo
* @return {number}
*/
function _recursiveWithMemo(nums, targetSum, currentIndex, memo) {
if (typeof memo[currentIndex][targetSum] === 'undefined') {
memo[currentIndex][targetSum] = _recursive(nums, targetSum, currentIndex);
}
return memo[currentIndex][targetSum];
}
/**
*
* Solution 3: bottom-up dynamic programming.
* The meaning of `dp[i][j]` here is given the first i numbers in the array,
* what is the count of all subsets whose sum equals to j.
*
* Time: O(mn) m: `targetSum`
* Space: O(mn) <- for `dp`
*
* @param {number[]} nums
* @param {number} targetSum
* @return {number}
*/
function countOfSubsetSum3(nums, targetSum) {
const n = nums.length;
const dp = new Array(n + 1)
.fill(0)
.map(() => new Array(targetSum + 1).fill(0));
/**
* If the `targetSum = 0`, and we don't have any numbers, there's one way to
* get the target sum, that's why we have dp[0][0] = 1 here. For the remaining
* columns in the row 0, there's no way to make the sum to be >0, so they should
* be 0 (default value initialized above)
*/
dp[0][0] = 1;
for (let i = 1; i <= n; i++) {
for (let j = 0; j <= targetSum; j++) {
if (j >= nums[i - 1]) {
dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i - 1]];
} else {
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[n][targetSum];
}
/**
*
* Solution 4: bottom-up dynamic programming with reduced space.
*
* Time: O(mn) m: `targetSum`
* Space: O(m) <- for `dp`
*
* @param {number[]} nums
* @param {number} targetSum
* @return {number}
*/
function countOfSubsetSum4(nums, targetSum) {
const n = nums.length;
const dp = new Array(targetSum + 1).fill(0);
dp[0] = 1;
for (let i = 1; i <= n; i++) {
for (let j = targetSum; j >= nums[i - 1]; j--) {
dp[j] += dp[j - nums[i - 1]];
}
}
return dp[targetSum];
}
// Test
console.log(countOfSubsetSum1([1, 1, 2, 3], 4)); // 3
console.log(countOfSubsetSum1([1, 2, 7, 1, 5], 9)); // 3
console.log(countOfSubsetSum2([1, 1, 2, 3], 4)); // 3
console.log(countOfSubsetSum2([1, 2, 7, 1, 5], 9)); // 3
console.log(countOfSubsetSum3([1, 1, 2, 3], 4)); // 3
console.log(countOfSubsetSum3([1, 2, 7, 1, 5], 9)); // 3
console.log(countOfSubsetSum4([1, 1, 2, 3], 4)); // 3
console.log(countOfSubsetSum4([1, 2, 7, 1, 5], 9)); // 3