Skip to content

Commit ec77c60

Browse files
committed
feat(algorithms, hash table): custom sort string
1 parent 34cac4d commit ec77c60

4 files changed

Lines changed: 226 additions & 8 deletions

File tree

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# Custom Sort String
2+
3+
You are given two strings order and s. All the characters of order are unique and were sorted in some custom order
4+
previously.
5+
6+
Permute the characters of `s` so that they match the order that `order` was sorted. More specifically, if a character `x`
7+
occurs before a character `y` in `order`, then `x` should occur before `y` in the permuted string.
8+
9+
Return any permutation of `s` that satisfies this property.
10+
11+
## Examples
12+
13+
Example 1:
14+
15+
```text
16+
Input: order = "cba", s = "abcd"
17+
Output: "cbad"
18+
19+
Explanation: "a", "b", "c" appear in order, so the order of "a", "b", "c" should be "c", "b", and "a".
20+
21+
Since "d" does not appear in order, it can be at any position in the returned string. "dcba", "cdba", "cbda" are also
22+
valid outputs.
23+
```
24+
25+
Example 2:
26+
27+
```text
28+
Input: order = "bcafg", s = "abcd"
29+
30+
Output: "bcad"
31+
32+
Explanation: The characters "b", "c", and "a" from order dictate the order for the characters in s. The character "d" in
33+
s does not appear in order, so its position is flexible.
34+
35+
Following the order of appearance in order, "b", "c", and "a" from s should be arranged as "b", "c", "a". "d" can be
36+
placed at any position since it's not in order. The output "bcad" correctly follows this rule. Other arrangements like
37+
"dbca" or "bcda" would also be valid, as long as "b", "c", "a" maintain their order.
38+
```
39+
40+
## Constraints
41+
42+
- 1 <= order.length <= 26
43+
- 1 <= s.length <= 200
44+
- order and s consist of lowercase English letters.
45+
- All the characters of order are unique.
46+
47+
## Topics
48+
49+
- Hash Table
50+
- String
51+
- Sorting
52+
53+
## Solution
54+
55+
### Custom Comparator
56+
57+
A comparator is a tool used to define (or redefine) an order between two items of the same class or data type. Most
58+
languages allow for the use of a custom comparator. This means that we can define a rule that determines how an array
59+
is sorted, and leverage built-in sort functions for custom sort.
60+
61+
Recall that a comparator takes two values c1 and c2 as parameters and returns the following:
62+
63+
1. If c1 comes before c2, return a negative integer.
64+
2. If c1 comes after c2, return a positive integer.
65+
3. If c1 and c2 are equal, return 0.
66+
67+
Letter c1 should come before c2 in the sorted order of s if and only if the index of c1 in the order string is less than
68+
the index of c2. By evaluating c1 and c2 as integer indices, we can use subtraction to achieve a return value that abides
69+
by the three rules described above.
70+
71+
Let's consider the following example: let s = "bdadeec" and order = "edcba". Letter "e" is at index 0 in order, whereas
72+
letter "b" is at index 3. Because 0<3, "e" should come before "b" in the result string. Therefore, the return result is
73+
0−3=−3, a negative number that adheres to the first rule listed above.
74+
75+
Taking into account all possible relationships between pairs of letters, the result string is "eeddcba".
76+
77+
Algorithm
78+
79+
- Create a character array of input string s to allow modification. (In languages like C++ where strings are mutable,
80+
we can sort s in-place without this step.)
81+
- Use the built-in sort method and define the comparator function as the difference between the index of c1 and the
82+
index of c2 in order.
83+
- Concatenate the character array into a string. (Skip if sorting was done in-place.)
84+
- Return this resulting string.
85+
86+
#### Complexity Analysis
87+
88+
Here, we define N as the length of string s, and K as the length of string order.
89+
90+
- Time Complexity: `O(NlogN)`
91+
Sorting an array of length N requires O(NlogN) time, and the indices of order have to be retrieved for each distinct
92+
letter, which results in an O(NlogN+K) complexity. K is at most 26, the number of unique English letters, so we can
93+
simplify the time complexity to O(NlogN).
94+
95+
- Space Complexity: `O(N) or O(logN)`
96+
Note that some extra space is used when we sort arrays in place. The space complexity of the sorting algorithm
97+
depends on the programming language.
98+
- In Java, Arrays.sort() is implemented using a variant of the Quick Sort algorithm, which has a space complexity of
99+
O(logN) for sorting two arrays. The Java solution also uses an auxiliary array of length N. This is the dominating
100+
term for the Java solution.
101+
- In C++, the sort() function is implemented as a hybrid of Quick Sort, Heap Sort, and Insertion Sort, with a worse-case
102+
space complexity of O(log⁡N). This is the main space used by the C++ solution.
103+
- In Python, the sorted() function uses Timsort, which has a space complexity of O(N). The Python solution also creates
104+
a list of length N from the input string.
105+
106+
### Frequency Table and Counting
107+
108+
Because the order string already gives us the explicit ordering to sort all the letters, we can generate a sorted
109+
version of s without calling upon an O(NlogN) algorithm. Let's create a frequency table where the key equals a character
110+
c, and the value equals how many times c appears in the string s. Then, for each character in order, append the number
111+
of occurrences of that character in s to the resulting string. After iterating through order, any remaining characters
112+
in s can be appended to the end without disrupting the defined sorting order.
113+
114+
Let's look at an example: consider s = "leetcoded" and order = "ecolt", and result is initially an empty string.
115+
116+
Frequency Table:
117+
118+
character l e t c o d
119+
frequency 1 3 1 1 1 2
120+
121+
- The first letter in order is "e", which appears 3 times in s, so result = "eee".
122+
- The second letter in order is "c", which appears 1 times in s, so result = "eeec".
123+
- The third letter in order is "o", which appears 1 times in s, so result = "eeeco".
124+
- The fourth letter in order is "l", which appears 1 times in s, so result = "eeecol".
125+
- The fifth letter in order is "t", which appears 1 times in s, so result = "eeecolt".
126+
127+
Finally, note that some letters in s could be missing in order, so we need to append any remaining letters to result.
128+
In this case, two occurrences of "d" need to be appended, so result = "eeecoltdd" is the final result.
129+
130+
**Algorithm**
131+
132+
- Initialize a frequency table (here we use a Hashmap, but a frequency array works too).
133+
- Populate the frequency table by incrementing freq[letter] for each letter in s.
134+
- For each character of order, append to result the same frequency it appears in s.
135+
- Iterate through the frequency table to find any remaining letters of s not in order, and append these letters to result.
136+
- Return the resulting string.
137+
138+
#### Complexity Analysis
139+
140+
Here, we define N as the length of string s, and K as the length of string order.
141+
142+
##### Time Complexity: O(N)
143+
144+
It takes O(N) time to populate the frequency table, and all other hashmap operations performed take O(1) time in the
145+
average case. Building the result string also takes O(N) time because each letter from s is appended to the result in
146+
the custom order, making the overall time complexity O(N).
147+
148+
##### Space Complexity: O(N)
149+
150+
A hash map and a result string are created, which results in an additional space complexity of O(N).
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from collections import Counter
2+
3+
4+
def custom_sort_string_custom_comparator(order: str, s: str) -> str:
5+
result = list(s)
6+
7+
result.sort(key=lambda c: order.index(c) if c in order else len(order))
8+
9+
return "".join(result)
10+
11+
12+
def custom_sort_string_frequency_table(order: str, s: str) -> str:
13+
# Create a hash map to store the count of each character in s
14+
s_elem_freq = Counter(s)
15+
# Define result to build the final string
16+
s_permutation = []
17+
18+
# Iterate through the order string
19+
for char in order:
20+
# Elem to be Ordered
21+
# Append char to result as many times as its recorded frequency
22+
if char in s_elem_freq:
23+
s_permutation.append(char * s_elem_freq[char])
24+
# Remove char from the hash map
25+
del s_elem_freq[char]
26+
27+
# Append remaining characters to result
28+
for unordered_elem in s_elem_freq:
29+
s_permutation.append(unordered_elem * s_elem_freq[unordered_elem])
30+
31+
# Return the result string
32+
return "".join(s_permutation)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import unittest
2+
from parameterized import parameterized
3+
from utils.test_utils import custom_test_name_func
4+
from algorithms.hash_table.custom_sort_string import (
5+
custom_sort_string_custom_comparator,
6+
custom_sort_string_frequency_table,
7+
)
8+
9+
CUSTOM_SORT_STRING_TEST_CASES = [
10+
("cba", "abcd", "cbad"),
11+
("bcafg", "abcd", "bcad"),
12+
("xyz", "abcdef", "abcdef"),
13+
("bca", "xyz", "xyz"),
14+
("edcba", "abcde", "edcba"),
15+
("wo", "meow", "wome"),
16+
]
17+
18+
19+
class CustomSortStringTestCase(unittest.TestCase):
20+
@parameterized.expand(
21+
CUSTOM_SORT_STRING_TEST_CASES, name_func=custom_test_name_func
22+
)
23+
def test_custom_sort_string(self, order: str, s: str, expected: str):
24+
actual = custom_sort_string_custom_comparator(order, s)
25+
self.assertEqual(expected, actual)
26+
27+
@parameterized.expand(
28+
CUSTOM_SORT_STRING_TEST_CASES, name_func=custom_test_name_func
29+
)
30+
def test_custom_sort_string_frequency_table(
31+
self, order: str, s: str, expected: str
32+
):
33+
actual = custom_sort_string_frequency_table(order, s)
34+
self.assertEqual(expected, actual)
35+
36+
37+
if __name__ == "__main__":
38+
unittest.main()

algorithms/matrix/game_of_life/__init__.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ def game_of_life(board: List[List[int]]) -> None:
1111

1212
# Pass 1: mark transitions in place
1313
# Encoding:
14-
# -1 : was 1, becomes 0 (live → dead)
15-
# 2 : was 0, becomes 1 (dead → live)
14+
# 1 : was 1, becomes 0 (live → dead)
15+
# 2 : was 0, becomes 1 (dead → live)
1616
for row in range(rows):
1717
for col in range(cols):
18-
liveNeighbors = 0
18+
live_neighbors = 0
1919

2020
# Count originally-live neighbors around (row, col)
2121
for i in range(3):
@@ -29,18 +29,16 @@ def game_of_life(board: List[List[int]]) -> None:
2929
# abs(...) == 1 is true for 1 and -1 (originally live),
3030
# and false for 0 and 2 (originally dead)
3131
if 0 <= r < rows and 0 <= c < cols and abs(board[r][c]) == 1:
32-
liveNeighbors += 1
32+
live_neighbors += 1
3333

3434
# Apply Conway's rules using the cell's original state
35-
if board[row][col] == 1 and (liveNeighbors < 2 or liveNeighbors > 3):
35+
if board[row][col] == 1 and (live_neighbors < 2 or live_neighbors > 3):
3636
board[row][col] = -1 # live → dead (under/overpopulation)
3737

38-
if board[row][col] == 0 and liveNeighbors == 3:
38+
if board[row][col] == 0 and live_neighbors == 3:
3939
board[row][col] = 2 # dead → live (reproduction)
4040

4141
# Pass 2: normalize markers to final 0/1 states
4242
for row in range(rows):
4343
for col in range(cols):
4444
board[row][col] = 1 if board[row][col] > 0 else 0
45-
46-
return board

0 commit comments

Comments
 (0)