Skip to content

Commit c05a654

Browse files
committed
Added question 416.
1 parent 92a1270 commit c05a654

File tree

1 file changed

+102
-0
lines changed

1 file changed

+102
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# 416. Partition Equal Subset Sum
2+
3+
## Recursive Solution
4+
- Run-time: O(2^N)
5+
- Space: O(N)
6+
- N = Number of Nums
7+
8+
Splitting an array into equal parts can be rephrased to, find a sub-array that is half of the total sum.
9+
With this we can generate a solution that focuses on finding a combination of numbers that contain such a sum.
10+
11+
To find this target sum, we can use recursion, simply ask two questions, do we want this number or don't want this number?
12+
13+
Lastly, there are some optimizations we can do, if we have an input of [1,1,1,...,1,1,100] that cannot be partitioned.
14+
We would end up repeating recursion calls for each 1, to keep 1 or not.
15+
When we end up not using the number, we would ask the same 2 questions again.
16+
This creates a time limit exceeded issue and we can optimize this by skipping repeated numbers when we don't want to use this number.
17+
18+
```
19+
class Solution:
20+
def canPartition(self, nums: List[int]) -> bool:
21+
22+
def does_subset_sum_exist(target, idx=0):
23+
if target == 0:
24+
return True
25+
if target < 0 or idx > len(nums)-1:
26+
return False
27+
if does_subset_sum_exist(target-nums[idx], idx+1):
28+
return True
29+
while idx+1 < len(nums) and nums[idx] == nums[idx+1]: # skip duplicate elements
30+
idx += 1
31+
return does_subset_sum_exist(target, idx+1)
32+
33+
if len(nums) == 0:
34+
return True
35+
num_sum = sum(nums)
36+
if num_sum % 2: # odd
37+
return False
38+
return does_subset_sum_exist(num_sum//2)
39+
```
40+
41+
## Dynamic Programming Solution
42+
- Run-time: O(T * N)
43+
- Space: O(T * N)
44+
- N = Number of Nums
45+
- T = Sum of Nums divided by 2
46+
47+
48+
The sub-problem of finding the subset sum is essentially a 0/1 knapsack problem.
49+
This particular variant of the knapsack problem can be solved using a 2d array of booleans.
50+
To identify that this is a 0/1 is the fact we cannot split the numbers, if we could then a greedy algorithm would work here. If splitting was allowed, we could sort it then take the smallest numbers until we reach the max and split the difference for the result.
51+
52+
We can construct a solution starting at sum 0 to the target sum.
53+
Each column will represent the nums and each row will be the sum.
54+
55+
```
56+
Given [1,2,3,4]
57+
Rows: sums from 0 to target sum
58+
Columns: numbers
59+
60+
Initial DP:
61+
0 1 2 3 4
62+
0 [[T, F, F, F, F],
63+
1 [F, F, F, F, F],
64+
2 [F, F, F, F, F],
65+
3 [F, F, F, F, F],
66+
4 [F, F, F, F, F],
67+
5 [F, F, F, F, F]]
68+
69+
Final DP:
70+
0 1 2 3 4
71+
0 [[T, T, T, T, T],
72+
1 [F, T, T, T, T],
73+
2 [F, F, T, T, T],
74+
3 [F, F, T, T, T],
75+
4 [F, F, F, T, T],
76+
5 [F, F, F, T, T]]
77+
```
78+
79+
The intuition is gathered by using the two questions we asked earlier, whether to keep this number or not.
80+
To not keep this number is simply looking at the current sum and the previous number.
81+
To keep this number, we have to look at the current sum - current number of the previous number.
82+
dp\[curr_sum][curr_num] = dp\[curr_sum][prev_num] or dp\[curr_sum-curr_num][prev_num]
83+
84+
```
85+
class Solution:
86+
def canPartition(self, nums: List[int]) -> bool:
87+
88+
def does_subset_sum_exist(target):
89+
dp = [[False] * (len(nums)+1) for _ in range(target+1)]
90+
dp[0][0] = True
91+
for curr_sum in range(0, target+1):
92+
for n_idx, num in enumerate(nums, 1):
93+
dp[curr_sum][n_idx] = dp[curr_sum][n_idx-1] or (dp[curr_sum-num][n_idx-1] if curr_sum-num >= 0 else False)
94+
return dp[-1][-1]
95+
96+
if len(nums) == 0:
97+
return True
98+
num_sum = sum(nums)
99+
if num_sum % 2: # odd
100+
return False
101+
return does_subset_sum_exist(num_sum//2)
102+
```

0 commit comments

Comments
 (0)