Skip to content

Commit 5348ac3

Browse files
Chris WuChris Wu
Chris Wu
authored and
Chris Wu
committed
no message
1 parent a125ccc commit 5348ac3

File tree

5 files changed

+241
-4
lines changed

5 files changed

+241
-4
lines changed

problems/combination-sum-ii.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""
2+
If you haven't see the [first problem](https://leetcode.com/problems/combination-sum/) and [explanation](https://leetcode.com/problems/combination-sum/discuss/386093/) already, I suggest you check it out first.
3+
4+
This Problem is similar to the [first one](https://leetcode.com/problems/combination-sum/).
5+
Except that we can only select each element in the `candidates` once.
6+
In the first problem, we can choose the same element in the `candidates` for many times as we like.
7+
In this problem, we can't.
8+
So we need to
9+
```
10+
helper(combination+[num], i+1, target_remain-num)
11+
```
12+
Instead of
13+
```
14+
helper(combination+[num], i, target_remain-num)
15+
```
16+
17+
Another problem is duplicates. For example,
18+
```
19+
candidates = [10,1,2,7,6,1,5]
20+
target = 8
21+
```
22+
We might end up choosing the fist 1 or second 1 and make the same combination `[1, 2, 5]`
23+
So I use a hash set to prevent duplicate.
24+
"""
25+
class Solution(object):
26+
def combinationSum2(self, candidates, target):
27+
def helper(combination, start, target_remain):
28+
if target_remain==0:
29+
answer.add(tuple(combination))
30+
for i in xrange(start, len(candidates)):
31+
num = candidates[i]
32+
if num>target_remain: break
33+
helper(combination+[num], i+1, target_remain-num)
34+
35+
answer = set()
36+
candidates.sort()
37+
helper([], 0, target)
38+
return [list(combination) for combination in answer]

problems/combination-sum.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
.
4343
4444
helper([7], 3, 0) --> bingo
45-
4645
```
4746
"""
4847
class Solution(object):
@@ -51,9 +50,9 @@ def helper(combination, start, target_remain):
5150
if target_remain==0:
5251
answer.append(combination)
5352
for i in xrange(start, len(candidates)):
54-
n = candidates[i]
55-
if n>target_remain: break
56-
helper(combination+[n], i, target_remain-n)
53+
num = candidates[i] #try out if with num adding into combination can make target_remain 0
54+
if num>target_remain: break
55+
helper(combination+[num], i, target_remain-num)
5756

5857
candidates.sort()
5958
answer = []

problems/combinations.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
class Solution(object):
2+
def combine(self, N, K):
3+
def helper(first, combination):
4+
if len(combination)==K:
5+
answer.append(combination)
6+
else:
7+
for num in xrange(first, N+1):
8+
helper(num+1, combination+[num])
9+
answer = []
10+
helper(1, [])
11+
return answer
12+
"""
13+
`helper()` helps append `combination` to the `answer` if there are already K element in the `combination`.
14+
15+
The parameter `first` means we are only going to use number fisrt~N.
16+
Because we used the smaller number already, or we will have duplicates.
17+
18+
If there are not enough element in the `combination`, it will run through fisrt~N and append all the element `num` to the `combination`, and set the `first` to num+1.
19+
In other words, if we use 3, we will not use 3 and the number below 3 anymore.
20+
21+
The call stack will be
22+
```
23+
N = 4, K = 2.
24+
25+
helper(1, [])
26+
helper(2, [1])
27+
helper(3, [1, 2])
28+
helper(4, [1, 3])
29+
helper(5, [1, 4])
30+
31+
helper(3, [2])
32+
helper(4, [2, 3])
33+
helper(5, [2, 4])
34+
35+
helper(4, [3])
36+
helper(5, [3, 4])
37+
38+
helper(5, [4])
39+
```
40+
"""
41+
42+
class Solution(object):
43+
def combine(self, N, K):
44+
answer = []
45+
combination = []
46+
first = 1
47+
while True:
48+
if len(combination)==K:
49+
answer.append(combination[:])
50+
51+
if len(combination)==K or first>N:
52+
if not combination: return answer
53+
first = combination.pop()+1 #backtrack
54+
else:
55+
combination.append(first)
56+
first+=1
57+
"""
58+
Iterative solution is not so easy to understand.
59+
I suggest you run a easier example on paper, and you will know how it works. (`N = 4 , K = 2`)
60+
For every iterative, we append the combination to the answer if the length is equal to K already.
61+
Adjust the `first` whenever needed (`len(combination)==K or first>N`), if not, keep on appending.
62+
63+
The time complexity is O(N!/(N!(N-K)!)), combination N choose K.
64+
The space complexity is O(N!/(N!(N-K)!)), combination N choose K.
65+
"""
66+
67+
68+
69+
70+
71+
72+
73+

problems/subsets-ii.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""
2+
If we use the solution from the [last question](https://leetcode.com/problems/subsets/),
3+
We will notice that it will have some duplicates.
4+
This is because if you have three 2 (`[2, 2, 2]`).
5+
You might choosing two different sets of 2 and end up looking the same.
6+
For example, you choose the first and the second => `[2, 2]`
7+
You choose the second and the third => `[2, 2]`
8+
9+
One workaround I did is just using hash-set.
10+
```
11+
return list(set([tuple(combination) for combination in answer]))
12+
```
13+
This cause us really high time complexity.
14+
Since turning list to tuple and tuple to list is actually O(N) in theory.
15+
16+
Time complexity would be `O((2^N) * N)` be cuase for each element in the answer we need to convert it to tuple and back.
17+
"""
18+
class Solution(object):
19+
def subsetsWithDup(self, nums):
20+
nums.sort()
21+
answer = [[]]
22+
for num in nums:
23+
new_subs = []
24+
for sub in answer:
25+
new_subs.append(sub+[num])
26+
answer.extend(new_subs)
27+
return list(set([tuple(combination) for combination in answer]))
28+
29+
"""
30+
I found the best solution and explaination is made by @ZitaoWang's answer and here is his [explaination](https://leetcode.com/problems/subsets-ii/discuss/171626/Python-solution). I copied from it.
31+
For example, `nums = [1,1,2,2,2,4,4,5]`. In this case, `counter = {1:2, 2:3, 4:2, 5:1}`.
32+
We intialize `answer = [[]]` and build the solution iteratively as we loop over the counter.
33+
We first reach `num, count = 1, 2`. The power set of [1,1] is `[[], [1], [1,1]]`.
34+
Then we reach `num, count = 2, 3`.
35+
The power set of [1,1,2,2,2] is obtained by appending either zero, one, two or three 2's to all elements in `answer`.
36+
After which we get answer = [[], [1], [1,1], [2], [1,2], [1,1,2],[2,2], [1,2,2], [1,1,2,2],[2,2,2], [1,2,2,2], [1,1,2,2,2]].
37+
After we loop over counter, answer will be the power set of nums.
38+
39+
Time complexity: `O(2^N)`, space complexity: `O(2^N)`.
40+
"""
41+
from collections import Counter
42+
43+
class Solution(object):
44+
def subsetsWithDup(self, nums):
45+
counter = Counter(nums)
46+
answer = [[]]
47+
48+
for num, count in counter.items():
49+
power_set = [[num]*c for c in xrange(1, count+1)]
50+
for i in xrange(len(answer)):
51+
for s in power_set:
52+
answer.append(answer[i]+s)
53+
return answer

problems/subsets.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
"""
2+
For every number in `nums` you either take (O) or not take it (X).
3+
So for example
4+
```
5+
nums = [1,2,3]
6+
7+
Subset [] is [X,X,X]
8+
Subset [1] is [O,X,X] => [1,X,X]
9+
Subset [2] is [X,O,X] => [X,2,X]
10+
Subset [3] is [X,X,O] => [X,X,3]
11+
Subset [1,2] is [O,O,X] => [1,2,X]
12+
Subset [1,3] is [O,X,O] => [1,X,3]
13+
.
14+
.
15+
.
16+
17+
Subset [1,2,3] is [O,O,O] => [1,2,3]
18+
```
19+
So there are total 2x2x2 posibilities (subsets).
20+
Because for each number, it has two choices, take (O) or not take (X).
21+
"""
22+
23+
24+
class Solution(object):
25+
def subsets(self, nums):
26+
def helper(i, combination):
27+
answer.add(tuple(combination))
28+
if i>=len(nums): return
29+
helper(i+1, combination+[nums[i]])
30+
helper(i+1, combination)
31+
32+
answer = set()
33+
helper(0, [])
34+
return [list(combination) for combination in answer]
35+
"""
36+
`helper()` help add `combination` to the answer.
37+
And if we have run through all the numbers, return.
38+
If not, for this number at index `i`, there are two scenarios.
39+
Add it to the combination, or not.
40+
41+
And there might be duplicates, and I could not think of better way without using hash set on `answer`.
42+
43+
The time complexity is O(2^N). The space complexity is O(2^N), too.
44+
"""
45+
46+
47+
class Solution(object):
48+
def subsets(self, nums):
49+
answer = [[]]
50+
for n in nums:
51+
new_subs = []
52+
for sub in answer:
53+
new_subs.append(sub+[n])
54+
answer.extend(new_subs)
55+
return answer
56+
"""
57+
So for each n in `nums`, what we do is
58+
```
59+
new_subs = []
60+
for sub in answer:
61+
new_subs.append(sub+[n])
62+
answer.extend(new_subs)
63+
```
64+
We take all the subset in the `answer`, append n, put the new subset into `new_subs`.
65+
And the answer become `subsets` + `new subsets`.
66+
You can think of it as
67+
`subsets` => the combination which we did not take n.
68+
`new subsets` => the combination which we take n.
69+
70+
Now if we have iterated the third element, then the `answer` now contains all the possible subsets, the combination which we took the third element, and the combination which we did not take the third element.
71+
72+
The time complexity is O(2^N). The space complexity is O(2^N), too.
73+
(This solution is in spired by @ZitaoWang's elegant solution)
74+
"""

0 commit comments

Comments
 (0)