Skip to content

Commit 1f89788

Browse files
committed
feat(algorithms, backtracking, subsets): generate permutations
1 parent 57e2116 commit 1f89788

11 files changed

Lines changed: 162 additions & 23 deletions

algorithms/backtracking/permutations/generate_permutations/README.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,74 @@ Input:
2020
1. letters = abc
2121
- Output: abc acb bac bca cab cba
2222

23+
24+
## Constraints
25+
26+
- All characters in word are unique.
27+
- 1 ≤ word.length ≤ 6
28+
- All characters in word are lowercase English letters.
29+
30+
## Solution
31+
32+
Problems such as this one, where we need to find the combinations or permutations of a given string, are good examples
33+
to solve using the subsets pattern as it describes an efficient Depth-First Search (DFS) approach to handle all these
34+
problems.
35+
36+
Let’s discuss a few basics first. We know that n! is the number of permutations for a set of size n. Another obvious and
37+
important concept is that if we choose an element for the first position, then the total permutations of the remaining
38+
elements are (n−1)!.
39+
40+
For example, if we’re given the string “abcd” and we pick “a” as our first element, then for the remaining elements we
41+
have the following permutations:
42+
43+
![Solution Sample](./images/solutions/generate_permutations_solution_sample.png)
44+
45+
Similarly, if we pick “b” as the first element, permute “acd”, and prepend each permutation with “b”, we can observe a
46+
pattern here as shown in the illustration above. That pattern tells us how to find all remaining permutations for each
47+
character in the given string.
48+
49+
We can do this recursively to find all permutations of substrings, such as “bcd”, “acd”, and so on. This implies that
50+
generating all possible permutations of the given string involves exploring different combinations of characters, which
51+
can be done efficiently using the subset technique. The key idea is to take one character of the given string at a time
52+
and find all the permutations that start with this chosen character. For this, imagine filling empty positions equal to
53+
the length of the string in the following manner: place the chosen character at the first position, then against this
54+
character, try all the remaining characters in the second position. Next, for each pair of characters in the first and
55+
second positions, try all the remaining characters in the third position. Keep doing this until we reach the last
56+
position to be filled. This process will allow us to systematically arrange each character in different positions and
57+
generate all possible permutations of the given string.
58+
59+
Here is a visual representation of all recursions for input string “bad”:
60+
61+
![Solution 1](./images/solutions/generate_permutations_solution_1.png)
62+
![Solution 2](./images/solutions/generate_permutations_solution_2.png)
63+
![Solution 3](./images/solutions/generate_permutations_solution_3.png)
64+
![Solution 4](./images/solutions/generate_permutations_solution_4.png)
65+
![Solution 5](./images/solutions/generate_permutations_solution_5.png)
66+
![Solution 6](./images/solutions/generate_permutations_solution_6.png)
67+
![Solution 7](./images/solutions/generate_permutations_solution_7.png)
68+
69+
We create a recursive function to compute the permutations of the string that has been passed as input. The function
70+
behaves in the following way:
71+
72+
- We fix the first character of the input string and swap it with its immediate next character.
73+
- We swap the indexes and get a new permutation of the string, which is stored in the variable, swapped_str.
74+
- The recursive call for the function increments the index by adding 1 to the current_index variable to compute the next
75+
permutation.
76+
- All permutations of the string are stored in the result array
77+
78+
### Time Complexity
79+
80+
Let’s anaylze the time complexity of the solution code above:
81+
- `permute_word()`: There are n! (factorial of n) permutations of a string of length n.
82+
- `permute_string()`: This is a recursive function that generates these permutations. For a string of length n, there
83+
are n recursive calls at the first level, `n-1` calls for the second, and so on. This results in a total of
84+
`n * (n-1) * (n-2) * ... * 1 = n!` calls
85+
- `swap_char()`: The swap function has a time complexity of O(n) since it involves creating a list from the string and
86+
then joining it back into a string after the swap. This operation is done for each recursive call.
87+
88+
So the overall time complexity is O(n * n!)
89+
90+
### Space Complexity
91+
92+
The space complexity of this solution is dependent on the depth of the recursive call stack. The maximum depth of
93+
recursion is n, so the space complexity is O(n).

algorithms/backtracking/permutations/generate_permutations/__init__.py

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,16 @@ def generate_permutations(letters: str) -> List[str]:
1919
The total space complexity is given by the amount of space required by the strings we're constructing. Like the
2020
time complexity, the space complexity is also O(n * n!).
2121
"""
22+
result = []
23+
word_len = len(letters)
24+
used_letters = [False] * word_len
25+
start = 0
26+
path_taken = []
2227

23-
def dfs(
24-
start_index: int, path: List[str], used: List[bool], res: List[str]
25-
) -> None:
26-
if start_index == len(letters):
27-
res.append("".join(path))
28+
def dfs(start_index: int, path: List[str], used: List[bool]) -> None:
29+
nonlocal result
30+
if start_index == word_len:
31+
result.append("".join(path))
2832
return
2933

3034
for i, letter in enumerate(letters):
@@ -35,16 +39,43 @@ def dfs(
3539
# add the letter to permutation, mark the letter as used
3640
path.append(letter)
3741
used[i] = True
38-
dfs(start_index + 1, path, used, res)
42+
dfs(start_index + 1, path, used)
3943
# remove the letter from permutation, mark the letter as unused
4044
path.pop()
4145
used[i] = False
4246

47+
dfs(start, path_taken, used_letters)
48+
49+
return result
50+
51+
52+
def permute_word(word: str) -> List[str]:
4353
result = []
44-
used_letters = [False] * len(letters)
45-
start = 0
46-
path_taken = []
54+
word_len = len(word)
55+
56+
def permute_str(letters: str, current_index):
57+
nonlocal result
58+
if current_index == word_len - 1:
59+
result.append(letters)
60+
return
61+
62+
for idx in range(current_index, word_len):
63+
swapped_str = swap_char(letters, current_index, idx)
64+
permute_str(swapped_str, current_index + 1)
4765

48-
dfs(start, path_taken, used_letters, result)
66+
def swap_char(letters: str, i: int, j: int) -> str:
67+
"""
68+
Swaps ith and jth indexes of the given string
69+
Args:
70+
letters (str): letters to find permutations for
71+
i (int): index of the first letter
72+
j (int): index of the second letter
73+
Returns:
74+
str: string with the letters at the ith and jth position swapped
75+
"""
76+
swap_index = list(letters)
77+
swap_index[i], swap_index[j] = swap_index[j], swap_index[i]
78+
return "".join(swap_index)
4979

80+
permute_str(word, 0)
5081
return result
19.8 KB
Loading
30.7 KB
Loading
35 KB
Loading
47 KB
Loading
54.5 KB
Loading
66.7 KB
Loading
36.4 KB
Loading
6.36 KB
Loading

0 commit comments

Comments
 (0)