Skip to content

Commit 40d9542

Browse files
committed
Update __init__.py
1 parent a71618f commit 40d9542

1 file changed

Lines changed: 116 additions & 0 deletions

File tree

backtracking/__init__.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
"""
2+
In this problem, we want to determine all possible combinations of k
3+
numbers out of 1 ... n. We use backtracking to solve this problem.
4+
5+
Time complexity: O(C(n,k)) which is O(n choose k) = O((n!/(k! * (n - k)!))),
6+
"""
7+
8+
from __future__ import annotations
9+
10+
from itertools import combinations
11+
12+
13+
def combination_lists(n: int, k: int) -> list[list[int]]:
14+
"""
15+
Generates all possible combinations of k numbers out of 1 ... n using itertools.
16+
17+
>>> combination_lists(n=4, k=2)
18+
[[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
19+
"""
20+
return [list(x) for x in combinations(range(1, n + 1), k)]
21+
22+
23+
def generate_all_combinations(n: int, k: int) -> list[list[int]]:
24+
"""
25+
Generates all possible combinations of k numbers out of 1 ... n using backtracking.
26+
27+
>>> generate_all_combinations(n=4, k=2)
28+
[[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
29+
>>> generate_all_combinations(n=0, k=0)
30+
[[]]
31+
>>> generate_all_combinations(n=10, k=-1)
32+
Traceback (most recent call last):
33+
...
34+
ValueError: k must not be negative
35+
>>> generate_all_combinations(n=-1, k=10)
36+
Traceback (most recent call last):
37+
...
38+
ValueError: n must not be negative
39+
>>> generate_all_combinations(n=5, k=4)
40+
[[1, 2, 3, 4], [1, 2, 3, 5], [1, 2, 4, 5], [1, 3, 4, 5], [2, 3, 4, 5]]
41+
>>> generate_all_combinations(n=3, k=3)
42+
[[1, 2, 3]]
43+
>>> generate_all_combinations(n=3, k=1)
44+
[[1], [2], [3]]
45+
>>> generate_all_combinations(n=1, k=0)
46+
[[]]
47+
>>> generate_all_combinations(n=1, k=1)
48+
[[1]]
49+
>>> from itertools import combinations
50+
>>> all(generate_all_combinations(n, k) == combination_lists(n, k)
51+
... for n in range(1, 6) for k in range(1, 6))
52+
True
53+
"""
54+
if k < 0:
55+
raise ValueError("k must not be negative")
56+
if n < 0:
57+
raise ValueError("n must not be negative")
58+
59+
result: list[list[int]] = []
60+
create_all_state(1, n, k, [], result)
61+
return result
62+
63+
64+
def create_all_state(
65+
increment: int,
66+
total_number: int,
67+
level: int,
68+
current_list: list[int],
69+
total_list: list[list[int]],
70+
) -> None:
71+
"""
72+
Helper function to recursively build all combinations.
73+
74+
>>> create_all_state(1, 4, 2, [], result := [])
75+
>>> result
76+
[[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
77+
>>> create_all_state(1, 3, 3, [], result := [])
78+
>>> result
79+
[[1, 2, 3]]
80+
>>> create_all_state(2, 2, 1, [1], result := [])
81+
>>> result
82+
[[1, 2]]
83+
>>> create_all_state(1, 0, 0, [], result := [])
84+
>>> result
85+
[[]]
86+
>>> create_all_state(1, 4, 0, [1, 2], result := [])
87+
>>> result
88+
[[1, 2]]
89+
>>> create_all_state(5, 4, 2, [1, 2], result := [])
90+
>>> result
91+
[]
92+
"""
93+
if level == 0:
94+
total_list.append(current_list[:])
95+
return
96+
97+
for i in range(increment, total_number - level + 2):
98+
current_list.append(i)
99+
create_all_state(i + 1, total_number, level - 1, current_list, total_list)
100+
current_list.pop()
101+
102+
103+
if __name__ == "__main__":
104+
from doctest import testmod
105+
106+
testmod()
107+
print(generate_all_combinations(n=4, k=2))
108+
tests = ((n, k) for n in range(1, 5) for k in range(1, 5))
109+
for n, k in tests:
110+
print(n, k, generate_all_combinations(n, k) == combination_lists(n, k))
111+
112+
print("Benchmark:")
113+
from timeit import timeit
114+
115+
for func in ("combination_lists", "generate_all_combinations"):
116+
print(f"{func:>25}(): {timeit(f'{func}(n=4, k = 2)', globals=globals())}")

0 commit comments

Comments
 (0)