Skip to content

Commit 88ceada

Browse files
authored
Merge pull request #169 from BrianLusina/feat/datastructures-trees
feat(datastructures, trees): tree traversal algorithms
2 parents e39dabd + 1536489 commit 88ceada

186 files changed

Lines changed: 4358 additions & 931 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

DIRECTORY.md

Lines changed: 43 additions & 12 deletions
Large diffs are not rendered by default.
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
# Letter Tile Possibilities
2+
3+
You are given a string, tiles, consisting of uppercase English letters. You can arrange the tiles into sequences of any
4+
length (from 1 to the length of tiles), and each sequence must include at most one tile, tiles[i], from tiles.
5+
6+
Your task is to return the number of possible non-empty unique sequences you can make using the letters represented on
7+
tiles[i].
8+
9+
## Constraints:
10+
11+
- 1 ≤ `tiles.length` ≤ 7
12+
- The `tiles` string consists of uppercase English letters.
13+
14+
## Examples
15+
16+
Example 1:
17+
18+
```text
19+
Input: tiles = "AAB"
20+
Output: 8
21+
Explanation: The possible sequences are "A", "B", "AA", "AB", "BA", "AAB", "ABA", "BAA".
22+
```
23+
24+
Example 2:
25+
```text
26+
Input: tiles = "AAABBC"
27+
Output: 188
28+
```
29+
30+
Example 3:
31+
```text
32+
Input: tiles = "V"
33+
Output: 1
34+
```
35+
36+
## Topics
37+
38+
- Hash Table
39+
- String
40+
- Backtracking
41+
- Counting
42+
43+
## Solutions
44+
45+
### Permutations and Combinations
46+
47+
This problem would have been straightforward if the tiles had no duplicates and we only needed to find sequences of the
48+
same length as the given tiles. In that case, we could simply calculate the total unique sequences using n! (where n is
49+
the length of the tiles).
50+
51+
![Sample](./images/examples/letter_tile_possibilities_example_sample.png)
52+
53+
However, in this problem, we need to find all unique sequences of any length—from 1 to the full length of the tiles—while
54+
accounting for duplicate tiles. To achieve this, we take the following approach:
55+
56+
We begin by sorting the letters in tiles so that similar letters are grouped. This allows us to systematically explore
57+
possible letter sets without repeating unnecessary work.
58+
59+
To generate sequences of lengths ranging from 1 to n, we consider each letter one by one. We start with a single
60+
character, determine its possible sequences, then move to two-character combinations, find their possible sequences,
61+
and so on. For each new character, we have two choices: either include the current letter in our selection or skip it
62+
and move to the next one. By exploring both choices, we generate all possible letter sets.
63+
64+
To prevent counting the same sequences multiple times due to duplicate letters, we ensure (in the previous step) that
65+
duplicate letter sets are not counted more than once while generating different letter sets. Then, for each set, we
66+
calculate how many distinct sequences can be formed by applying the updated formula for permutations, which accounts
67+
for repeated letters: `n! / c!`, where c is the count of each repeated letter.
68+
69+
The more repeated letters in a set, the fewer unique ways it can be arranged.
70+
71+
We repeat this process for every possible letter set. Eventually, we sum the count of all these possible sequences to
72+
obtain the total number of unique non-empty sequences. Finally, we subtract one from our final count to exclude the
73+
empty set, as it does not contribute to valid arrangements.
74+
75+
Let’s look at the following illustration to understand this better.
76+
77+
![Solution 0](./images/solutions/letter_tile_possibilities_solution_0.png)
78+
79+
Now, let’s look at the algorithm steps of this solution:
80+
81+
- Initialize a set, unique_letter_sets, to track unique letter sets.
82+
- Sort the input string, tiles, to group identical letters together.
83+
- Generate unique letter sets and count their permutations:
84+
- The function generate_sequences is used to recursively generate all possible letter subsets.
85+
- We calculate the number of valid sequences using `count_permutations` for each unique letter set. The
86+
`count_permutations` uses the helper function `factorial(n)`, which computes the factorial of a given number.
87+
- The recursive function, `generate_sequences(tiles, current_letter_set, index, unique_letter_sets)`, works as follows:
88+
- **Base case**: If `index` reaches the end of tiles, check if `current_letter_set` is unique. If it is:
89+
- Add it to `unique_letter_sets`.
90+
- Compute its permutations and return the count.
91+
- **Recursive cases**:
92+
- Exclude the current character (move to the next index). Store its result in the variable `without_letter`.
93+
- Include the current character (append it and move to the next index). Store its result in the variable `with_letter`.
94+
- The sum of both recursive calls gives the total count of valid sequences.
95+
- Once all the letters in tiles have been explored, return the output. As an empty set is also considered in the
96+
recursive process, we subtract 1 before returning the output.
97+
98+
![Solution 1](./images/solutions/letter_tile_possibilities_solution_1.png)
99+
![Solution 2](./images/solutions/letter_tile_possibilities_solution_2.png)
100+
![Solution 3](./images/solutions/letter_tile_possibilities_solution_3.png)
101+
![Solution 4](./images/solutions/letter_tile_possibilities_solution_4.png)
102+
![Solution 5](./images/solutions/letter_tile_possibilities_solution_5.png)
103+
![Solution 6](./images/solutions/letter_tile_possibilities_solution_6.png)
104+
![Solution 7](./images/solutions/letter_tile_possibilities_solution_7.png)
105+
![Solution 8](./images/solutions/letter_tile_possibilities_solution_8.png)
106+
![Solution 9](./images/solutions/letter_tile_possibilities_solution_9.png)
107+
![Solution 10](./images/solutions/letter_tile_possibilities_solution_10.png)
108+
![Solution 11](./images/solutions/letter_tile_possibilities_solution_11.png)
109+
![Solution 12](./images/solutions/letter_tile_possibilities_solution_12.png)
110+
![Solution 13](./images/solutions/letter_tile_possibilities_solution_13.png)
111+
![Solution 14](./images/solutions/letter_tile_possibilities_solution_14.png)
112+
![Solution 15](./images/solutions/letter_tile_possibilities_solution_15.png)
113+
![Solution 16](./images/solutions/letter_tile_possibilities_solution_16.png)
114+
![Solution 17](./images/solutions/letter_tile_possibilities_solution_17.png)
115+
![Solution 18](./images/solutions/letter_tile_possibilities_solution_18.png)
116+
![Solution 19](./images/solutions/letter_tile_possibilities_solution_19.png)
117+
![Solution 10](./images/solutions/letter_tile_possibilities_solution_20.png)
118+
![Solution 11](./images/solutions/letter_tile_possibilities_solution_21.png)
119+
120+
#### Time Complexity
121+
122+
Let’s break down and analyze the time complexity of this solution:
123+
- Sorting the input string takes `O(n log(n))` time, where n is the length of the tiles.
124+
- The recursive function explores all possible subsets of the given tiles. As each tile can either be included or
125+
skipped, there are 2^n subsets in the worst case.
126+
- For each subset, we calculate the number of unique sequences that can be formed using the formula that accounts for
127+
duplicate letters.
128+
- Computing the factorial and handling character frequencies takes at most `O(n)` time per subset.
129+
- As we do this for all 2^n subsets, the total time complexity is `O(2 ^n × n)`, which dominates the sorting step.
130+
131+
If we sum these up, the overall time complexity simplifies to:
132+
133+
`O(nlogn) + O(2^n) + O(2^n×n) = O(2^n×n)`
134+
135+
#### Space Complexity
136+
Let’s analyze the space complexity of this solution:
137+
- As the recursion goes as deep as the length of the string n, the worst case space used by the function calls is `O(n)`.
138+
- We store all unique subsets of tiles in a set, which can hold up to O(2^n) subsets in the worst case. Each sequence in
139+
the set can be up to length n. So, the set uses `O(2^n × n)` space.
140+
- The permutation calculation uses extra space, but this is at most O(n).
141+
142+
If we add these up, the overall space complexity simplifies to:
143+
144+
`O(n) + O(2^n × n) + O(n) = O(2^n × n)`
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
from typing import List, Set
2+
from itertools import permutations
3+
4+
5+
def num_tile_possibilities(tiles: str) -> int:
6+
# Store unique sequences to avoid duplicates
7+
unique_letter_sets: Set[str] = set()
8+
9+
# Sort characters to handle duplicates efficiently
10+
sorted_tiles: str = "".join(sorted(tiles))
11+
12+
def generate_sequences(current: str, index: int):
13+
# Recursively generates all possible sequences by including/excluding each tile.
14+
# Once a new sequence is found, its unique permutations are counted.
15+
if index >= len(sorted_tiles):
16+
# If a new sequence is found, count its unique permutations
17+
if current not in unique_letter_sets:
18+
unique_letter_sets.add(current)
19+
20+
total_permutations = len(set(permutations(current)))
21+
return total_permutations
22+
return 0
23+
24+
# Explore two possibilities: skipping the current character or including it
25+
without_letter = generate_sequences(current, index=index + 1)
26+
with_letter = generate_sequences(current + sorted_tiles[index], index=index + 1)
27+
28+
# Return all the possible sequences
29+
return without_letter + with_letter
30+
31+
# Optionally, you can write the count_permutations and the factorial method if not using builtin methods
32+
# def factorial(n):
33+
#
34+
# # Computes the factorial of a given number.
35+
# if n <= 1:
36+
# return 1
37+
#
38+
# result = 1
39+
# for num in range(2, n + 1):
40+
# result *= num
41+
# return result
42+
#
43+
# def count_permutations(sequence):
44+
#
45+
# # Calculates the number of unique permutations of a sequence, accounting for duplicate characters.
46+
# permutations = factorial(len(sequence))
47+
#
48+
# # Adjust for repeated characters by dividing by factorial of their counts
49+
# for count in Counter(sequence).values():
50+
# permutations //= factorial(count)
51+
#
52+
# return permutations
53+
54+
output = generate_sequences("", 0)
55+
56+
return output - 1
57+
58+
59+
def num_tile_possibilities_with_recursion(tiles: str) -> int:
60+
sequences: Set[str] = set()
61+
used: List[bool] = [False] * len(tiles)
62+
63+
def generate_sequences(current: str) -> None:
64+
sequences.add(current)
65+
66+
for idx, char in enumerate(tiles):
67+
if not used[idx]:
68+
used[idx] = True
69+
generate_sequences(current + char)
70+
used[idx] = False
71+
72+
generate_sequences("")
73+
return len(sequences) - 1
74+
75+
76+
def num_tile_possibilities_with_optimized_recursion(tiles: str) -> int:
77+
char_count: List[int] = [0] * 26
78+
for letter in tiles:
79+
char_count[ord(letter) - ord("A")] += 1
80+
81+
def generate_sequences() -> int:
82+
result = 0
83+
for idx in range(26):
84+
if char_count[idx] == 0:
85+
continue
86+
87+
result += 1
88+
char_count[idx] -= 1
89+
result += generate_sequences()
90+
char_count[idx] += 1
91+
92+
return result
93+
94+
return generate_sequences()
17.8 KB
Loading
67 KB
Loading
17.6 KB
Loading
45.8 KB
Loading
38.1 KB
Loading
37.5 KB
Loading
43.5 KB
Loading

0 commit comments

Comments
 (0)