Skip to content

Commit d02644c

Browse files
committed
LeetCode 78. Subsets
1 parent f4e5b3a commit d02644c

File tree

3 files changed

+124
-1
lines changed

3 files changed

+124
-1
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Proposed solutions to some LeetCode problems. The first column links to the prob
4343
| [73. Set Matrix Zeroes][lc73] | 🟠 Medium | [![python](res/py.png)][lc73py] |
4444
| [74. Search a 2D Matrix][lc74] | 🟠 Medium | [![python](res/py.png)][lc74py] |
4545
| [76. Minimum Window Substring][lc76] | 🔴 Hard | [![python](res/py.png)][lc76py] |
46+
| [78. Subsets][lc78] | 🟠 Medium | [![python](res/py.png)][lc78py] |
4647
| [86. Partition List][lc86] | 🟠 Medium | [![python](res/py.png)][lc86py] |
4748
| [88. Merge Sorted Array][lc88] | 🟢 Easy | [![python](res/py.png)](leetcode/merge-sorted-array.py) |
4849
| [92. Reverse Linked List II][lc92] | 🟠 Medium | [![python](res/py.png)][lc92py] |
@@ -255,9 +256,12 @@ Proposed solutions to some LeetCode problems. The first column links to the prob
255256
[lc74py]: leetcode/search-a-2d-matrix.py
256257
[lc76]: https://leetcode.com/problems/minimum-window-substring/
257258
[lc76py]: leetcode/minimum-window-substring.py
259+
[lc78]: https://leetcode.com/problems/subsets/
260+
[lc78py]: leetcode/subsets.py
258261
[lc86]: https://leetcode.com/problems/partition-list/
259262
[lc86py]: leetcode/partition-list.py
260263
[lc88]: https://leetcode.com/problems/merge-sorted-array/
264+
[lc88py]: leetcode/merge-sorted-array.py
261265
[lc92]: https://leetcode.com/problems/reverse-linked-list-ii/
262266
[lc92py]: leetcode/reverse-linked-list-ii.py
263267
[lc97]: https://leetcode.com/problems/interleaving-string/

leetcode/lists/neetcode.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ From their website:
276276

277277
| | B75 | Level | Problem | Solutions |
278278
| :-: | --- | --------- | ------------------------------------------------- | ------------------------------------- |
279-
| | | 🟠 Medium | [78. Subsets][lc78] | |
279+
| | | 🟠 Medium | [78. Subsets][lc78] | [![python](../../res/py.png)][lc78py] |
280280
|| | 🟠 Medium | [39. Combination Sum][lc39] | [![python](../../res/py.png)][lc39py] |
281281
| | | 🟠 Medium | [46. Permutations][lc46] | |
282282
| | | 🟠 Medium | [90. Subsets II][lc90] | |
@@ -287,6 +287,7 @@ From their website:
287287
|| | 🔴 Hard | [51. N-Queens][lc51] | [![python](../../res/py.png)][lc51py] |
288288

289289
[lc78]: https://leetcode.com/problems/subsets/
290+
[lc78py]: ../subsets.py
290291
[lc39]: https://leetcode.com/problems/combination-sum/
291292
[lc39py]: ../combination-sum.py
292293
[lc46]: https://leetcode.com/problems/permutations/

leetcode/subsets.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# 78. Subsets
2+
# 🟠 Medium
3+
#
4+
# https://leetcode.com/problems/subsets/
5+
#
6+
# Tags: Array - Backtracking - Bit Manipulation
7+
8+
import timeit
9+
from typing import List
10+
11+
12+
# Iterate over all elements of the input, for each element, generate all
13+
# subsets that contain it and the ones that don't.
14+
#
15+
# Time complexity: O(n * 2^n) - For each element, we have two options,
16+
# take it or not, when we take it, we copy the result set in O(n) to
17+
# pass it to one of the branches.
18+
# Space complexity: O(2^n) - The result list will have 2^n elements.
19+
#
20+
# Runtime: 43 ms, faster than 76.59%
21+
# Memory Usage: 14.2 MB, less than 35.80%
22+
class Recursive:
23+
def subsets(self, nums: List[int]) -> List[List[int]]:
24+
# Define an array to hold the results.
25+
res = []
26+
# Define a recursive function that explores a given branch of
27+
# the recursion.
28+
def bt(idx: int, res: List[int]) -> List[List[int]]:
29+
# For each index, return the result of picking and not
30+
# picking this element.
31+
# Check for the base case when this is the last element.
32+
if idx == len(nums) - 1:
33+
return [res, res + [nums[idx]]]
34+
# If we are not at the last index, keep exploring.
35+
return bt(idx + 1, res) + bt(idx + 1, res + [nums[idx]])
36+
37+
# Initial call
38+
return bt(0, [])
39+
40+
41+
# We can implement the same solution without recursion by iterating
42+
# over the list and copying all the existing subsets adding the current
43+
# item that we are visiting.
44+
#
45+
# Time complexity: O(n * 2^n) - For each element, we have two options,
46+
# take it or not, when we take it, we copy all the existing subsets and
47+
# add this element to them before adding them to the previous result.
48+
# Space complexity: O(2^n) - The result list will have 2^n elements.
49+
#
50+
# Runtime: 36 ms, faster than 92.06%
51+
# Memory Usage: 14 MB, less than 82.71%
52+
class Iterative:
53+
def subsets(self, nums: List[int]) -> List[List[int]]:
54+
# Define an array to hold the results.
55+
res = [[]]
56+
# Iterate over the input, for each number, duplicate all
57+
# existing entries in the result set adding the current number.
58+
for num in nums:
59+
res += [curr + [num] for curr in res]
60+
return res
61+
62+
63+
# Another approach is to generate all possible bitmasks of the same
64+
# length as the number of elements in the input, then iterate over them
65+
# using their bit values to determine which elements to add to the
66+
# current subset. If a given bit i is 0, the subset does not contain the
67+
# element i, if it is 1, it does.
68+
#
69+
# Time complexity: O(n * 2^n) - We iterate over all possible bitmasks of
70+
# length n, for each we append a result to the result set, which is
71+
# amortized O(1)
72+
# Space complexity: O(2^n) - The result list will have 2^n elements.
73+
#
74+
# Runtime: 72 ms, faster than 10.69%
75+
# Memory Usage: 14.2 MB, less than 35.80%
76+
class BinarySorted:
77+
def subsets(self, nums: List[int]) -> List[List[int]]:
78+
n = len(nums)
79+
res = []
80+
# Use this range and slicing to avoid dealing with leading 0s
81+
# in the bitmask.
82+
for i in range(2**n, 2 ** (n + 1)):
83+
# Create the subset using the bitmask for this index.
84+
res.append([nums[j] for j in range(n) if bin(i)[3:][j] == "1"])
85+
return res
86+
87+
88+
def test():
89+
executors = [
90+
Recursive,
91+
Iterative,
92+
BinarySorted,
93+
]
94+
tests = [
95+
[[1, 2, 3], [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]],
96+
[[0], [[], [0]]],
97+
]
98+
for executor in executors:
99+
start = timeit.default_timer()
100+
for _ in range(1):
101+
for n, t in enumerate(tests):
102+
sol = executor()
103+
result = sol.subsets(t[0])
104+
exp = t[1]
105+
result.sort()
106+
exp.sort()
107+
assert result == exp, (
108+
f"\033[93m» {result} <> {exp}\033[91m for "
109+
+ f"test {n} using \033[1m{executor.__name__}"
110+
)
111+
stop = timeit.default_timer()
112+
used = str(round(stop - start, 5))
113+
cols = "{0:20}{1:10}{2:10}"
114+
res = cols.format(executor.__name__, used, "seconds")
115+
print(f"\033[92m» {res}\033[0m")
116+
117+
118+
test()

0 commit comments

Comments
 (0)