|
| 1 | +# 416. Partition Equal Subset Sum |
| 2 | +LeetCode problem: [416. Partition Equal Subset Sum](https://leetcode.com/problems/partition-equal-subset-sum/) |
| 3 | + |
| 4 | +## LeetCode problem description |
| 5 | +> Given an integer array nums, return true if you can partition the array into two subsets such that the sum of the elements in both subsets is equal or false otherwise. |
| 6 | +
|
| 7 | +``` |
| 8 | +Example 1: |
| 9 | +
|
| 10 | +Input: nums = [1,5,11,5] |
| 11 | +Output: true |
| 12 | +Explanation: The array can be partitioned as [1, 5, 5] and [11]. |
| 13 | +------------------------------------------------------------------------ |
| 14 | +
|
| 15 | +Example 2: |
| 16 | +
|
| 17 | +Input: nums = [1,2,3,5] |
| 18 | +Output: false |
| 19 | +Explanation: The array cannot be partitioned into equal sum subsets. |
| 20 | +
|
| 21 | +------------------------------------------------------------------------ |
| 22 | +Constraints: |
| 23 | +
|
| 24 | +1 <= nums.length <= 200 |
| 25 | +1 <= nums[i] <= 100 |
| 26 | +``` |
| 27 | + |
| 28 | +## Thoughts |
| 29 | +* When we first see this problem, we might want to loop through all subsets of the array. If there is a subset whose sum is equal to `half of the sum`, then return `true`. This can be achieved with a `backtracking algorithm`, but after seeing the constraint `nums.length <= 200`, we can estimate that the program will time out. |
| 30 | +* This is actually a `01 knapsack problem`. `01 knapsack problem` belongs to `dynamic programming`. `Dynamic programming` means that the answer to the current problem can be derived from the previous similar problem. Therefore, the `dp` array is used to record all the answers. |
| 31 | + |
| 32 | +* The core logic of the `01 knapsack problem` uses a two-dimensional `dp` array or a one-dimensional `dp` **rolling array**, first **traverses the items**, then **traverses the knapsack in reverse**, then **reference the previous value corresponding to the size of current 'item'**. |
| 33 | +* There are many things to remember when using a two-dimensional `dp` array, and it is difficult to write it right at once during an interview, so I won't describe it here. |
| 34 | + |
| 35 | +### Common steps in '01 Knapsack Problem' |
| 36 | +These five steps are a pattern for solving `dynamic programming` problems. |
| 37 | + |
| 38 | +1. Determine the **meaning** of the `dp[j]` |
| 39 | + * We can use a one-dimensional `dp` **rolling array**. Rolling an array means that the values of the array are overwritten each time through the loop. |
| 40 | + * At first, try to use the problem's `return` value as the value of `dp[j]` to determine the meaning of `dp[j]`. If it doesn't work, try another way. |
| 41 | + * So, `dp[j]` represents whether it is possible to `sum` the first `i` `nums` to get `j`. |
| 42 | + * The value of `dp[j]` is a boolean. |
| 43 | +2. Determine the `dp` array's initial value |
| 44 | + * Use an example: |
| 45 | + ``` |
| 46 | + nums = [1,5,11,5], so 'half of the sum' is 11. |
| 47 | + The `size` of the knapsack is `half of the sum`, and the `items` are `nums`. |
| 48 | + So after initialization, the 'dp' array would be: |
| 49 | + # 0 1 2 3 4 5 6 7 8 9 10 11 |
| 50 | + # T F F F F F F F F F F F # dp |
| 51 | + # 1 |
| 52 | + # 5 |
| 53 | + # 11 |
| 54 | + # 5 |
| 55 | + ``` |
| 56 | + * You can see the `dp` array size is one greater than the knapsack size. In this way, the backpack size and index value are equal, which helps to understand. |
| 57 | + * `dp[0]` is set to `true`, indicating that an empty backpack can be achieved by not putting any items in it. In addition, it is used as the starting value, and the subsequent `dp[j]` will depend on it. If it is `false`, all values of `dp[j]` will be `false`. |
| 58 | + * `dp[j] = false (j != 0)`, indicating that it is impossible to get `j` with no `nums`. |
| 59 | + |
| 60 | +3. Determine the `dp` array's recurrence formula |
| 61 | + * Try to complete the grid. In the process, you will get inspiration to derive the formula. |
| 62 | + ``` |
| 63 | + 1. Use the first num '1'. |
| 64 | + # 0 1 2 3 4 5 6 7 8 9 10 11 |
| 65 | + # T F F F F F F F F F F F |
| 66 | + # 1 T T F F F F F F F F F F # dp |
| 67 | + ``` |
| 68 | + ``` |
| 69 | + 2. Use the second num '5'. |
| 70 | + # 0 1 2 3 4 5 6 7 8 9 10 11 |
| 71 | + # T F F F F F F F F F F F |
| 72 | + # 1 T T F F F F F F F F F F |
| 73 | + # 5 T T F F F T T F F F F F |
| 74 | + ``` |
| 75 | + ``` |
| 76 | + 3. Use the third num '11'. |
| 77 | + # 0 1 2 3 4 5 6 7 8 9 10 11 |
| 78 | + # T F F F F F F F F F F F |
| 79 | + # 1 T T F F F F F F F F F F |
| 80 | + # 5 T T F F F T T F F F F F |
| 81 | + # 11 T T F F F T T F F F F T |
| 82 | + ``` |
| 83 | + ``` |
| 84 | + 3. Use the last num '5'. |
| 85 | + # 0 1 2 3 4 5 6 7 8 9 10 11 |
| 86 | + # T F F F F F F F F F F F |
| 87 | + # 1 T T F F F F F F F F F F |
| 88 | + # 5 T T F F F T T F F F F F |
| 89 | + # 11 T T F F F T T F F F F T |
| 90 | + # 5 T T F F F T T F F F T T # dp |
| 91 | + ``` |
| 92 | + * After analyzing the sample `dp` grid, we can derive the `Recurrence Formula`: |
| 93 | + ```python |
| 94 | + dp[j] = dp[j] or dp[j - nums[i]] |
| 95 | + ``` |
| 96 | +4. Determine the `dp` array's traversal order |
| 97 | + * `dp[j]` depends on `dp[j]` and `dp[j - nums[i]]`, so we should traverse the `dp` array from top to bottom, then **from right to left**. |
| 98 | + * Please think if we can traverse the `dp` array from top to bottom, then `from left to right`? In the `Python` code comments, I will answer this question. |
| 99 | +5. Check the `dp` array's value |
| 100 | + * Print the `dp` to see if it is as expected. |
| 101 | + |
| 102 | +### Complexity |
| 103 | +* Time: `O(n * sum/2)`. |
| 104 | +* Space: `O(sum/2)`. |
| 105 | + |
| 106 | +## Python |
| 107 | +```python |
| 108 | +class Solution: |
| 109 | + def canPartition(self, nums: List[int]) -> bool: |
| 110 | + sum_ = sum(nums) |
| 111 | + if sum_ % 2 == 1: |
| 112 | + return False |
| 113 | + |
| 114 | + dp = [False] * ((sum_ // 2) + 1) |
| 115 | + dp[0] = True |
| 116 | + |
| 117 | + for num in nums: |
| 118 | + for j in range(len(dp) - 1, 0, -1): # If traverse from left to right, the newly assigned value `dp[j - num]` will affect the subsequent `dp[j]`. This is wrong because each `num` can only be used once. |
| 119 | + if j < num: |
| 120 | + break |
| 121 | + dp[j] = dp[j] or dp[j - num] |
| 122 | + |
| 123 | + return dp[-1] |
| 124 | +``` |
| 125 | + |
| 126 | +## C++ |
| 127 | +```cpp |
| 128 | +class Solution { |
| 129 | +public: |
| 130 | + bool canPartition(vector<int>& nums) { |
| 131 | + auto sum = reduce(nums.begin(), nums.end()); |
| 132 | + if (sum % 2 == 1) |
| 133 | + return false; |
| 134 | + |
| 135 | + auto dp = vector<bool>(sum / 2 + 1); |
| 136 | + dp[0] = true; |
| 137 | + |
| 138 | + for (auto num : nums) |
| 139 | + for (auto j = dp.size() - 1; j >= num; j--) |
| 140 | + dp[j] = dp[j] || dp[j - num]; |
| 141 | + |
| 142 | + return dp[dp.size() - 1]; |
| 143 | + } |
| 144 | +}; |
| 145 | +``` |
| 146 | + |
| 147 | +## Java |
| 148 | +```java |
| 149 | +class Solution { |
| 150 | + public boolean canPartition(int[] nums) { |
| 151 | + var sum = IntStream.of(nums).sum(); |
| 152 | + if (sum % 2 == 1) |
| 153 | + return false; |
| 154 | + |
| 155 | + var dp = new boolean[sum / 2 + 1]; |
| 156 | + dp[0] = true; |
| 157 | + |
| 158 | + for (var num : nums) |
| 159 | + for (var j = dp.length - 1; j >= num; j--) |
| 160 | + dp[j] = dp[j] || dp[j - num]; |
| 161 | + |
| 162 | + return dp[dp.length - 1]; |
| 163 | + } |
| 164 | +} |
| 165 | +``` |
| 166 | + |
| 167 | +## C# |
| 168 | +```c# |
| 169 | +public class Solution { |
| 170 | + public bool CanPartition(int[] nums) { |
| 171 | + var sum = nums.Sum(); |
| 172 | + if (sum % 2 == 1) |
| 173 | + return false; |
| 174 | + |
| 175 | + var dp = new bool[sum / 2 + 1]; |
| 176 | + dp[0] = true; |
| 177 | + |
| 178 | + foreach (var num in nums) |
| 179 | + for (var j = dp.Length - 1; j >= num; j--) |
| 180 | + dp[j] = dp[j] || dp[j - num]; |
| 181 | + |
| 182 | + return dp[dp.Length - 1]; |
| 183 | + } |
| 184 | +} |
| 185 | +``` |
| 186 | + |
| 187 | +## JavaScript |
| 188 | +```javascript |
| 189 | +var canPartition = function(nums) { |
| 190 | + sum = _.sum(nums) |
| 191 | + if (sum % 2 == 1) |
| 192 | + return false |
| 193 | + |
| 194 | + dp = Array(sum / 2 + 1).fill(false) |
| 195 | + dp[0] = true |
| 196 | + |
| 197 | + for (num of nums) |
| 198 | + for (let j = dp.length - 1; j >= num; j--) |
| 199 | + dp[j] = dp[j] || dp[j - num] |
| 200 | + |
| 201 | + return dp.at(-1) |
| 202 | +}; |
| 203 | +``` |
| 204 | + |
| 205 | +## Go |
| 206 | +```go |
| 207 | +func canPartition(nums []int) bool { |
| 208 | + sum := 0 |
| 209 | + for _, num := range nums { |
| 210 | + sum += num |
| 211 | + } |
| 212 | + if sum % 2 == 1 { |
| 213 | + return false |
| 214 | + } |
| 215 | + |
| 216 | + dp := make([]bool, sum / 2 + 1) |
| 217 | + dp[0] = true |
| 218 | + |
| 219 | + for _, num := range nums { |
| 220 | + for j := len(dp) - 1; j >= num; j-- { |
| 221 | + dp[j] = dp[j] || dp[j - num] |
| 222 | + } |
| 223 | + } |
| 224 | + |
| 225 | + return dp[len(dp) - 1] |
| 226 | +} |
| 227 | +``` |
| 228 | + |
| 229 | +## Ruby |
| 230 | +```ruby |
| 231 | +def can_partition(nums) |
| 232 | + sum = nums.sum |
| 233 | + return false if sum % 2 == 1 |
| 234 | + |
| 235 | + dp = Array.new(sum / 2 + 1, false) |
| 236 | + dp[0] = true |
| 237 | + |
| 238 | + nums.each do |num| |
| 239 | + (1..(dp.size - 1)).reverse_each do |j| |
| 240 | + break if j < num |
| 241 | + dp[j] = dp[j] || dp[j - num] |
| 242 | + end |
| 243 | + end |
| 244 | + |
| 245 | + return dp[-1] |
| 246 | +end |
| 247 | +``` |
0 commit comments