791. Custom Sort String
Problem Description
You are given two strings: order
and s
. The string order
contains unique characters that define a custom sorting order. Your task is to rearrange the characters in string s
according to this custom order.
The sorting rule is: if character x
appears before character y
in the order
string, then all occurrences of x
must appear before all occurrences of y
in the rearranged version of s
.
For example:
- If
order = "cba"
ands = "abcd"
, then"cbad"
would be a valid result because:c
comes beforeb
inorder
, soc
comes beforeb
in the resultc
comes beforea
inorder
, soc
comes beforea
in the resultb
comes beforea
inorder
, sob
comes beforea
in the resultd
is not inorder
, so it can be placed anywhere
Characters in s
that don't appear in order
can be placed in any position relative to each other, as long as the relative order of characters that do appear in order
is maintained.
The solution creates a dictionary mapping each character in order
to its index position. Then it sorts string s
using this mapping as the sort key. Characters not in order
get a default value of 0, placing them at the beginning of the sorted result.
Intuition
The key insight is that we need to sort string s
based on a custom ordering rather than the standard alphabetical order. When we think about custom sorting, we need a way to assign each character a "priority" or "rank" that determines its position in the sorted output.
The natural approach is to use the position (index) of each character in the order
string as its sorting priority. For instance, if order = "cba"
, then:
c
has priority 0 (appears at index 0)b
has priority 1 (appears at index 1)a
has priority 2 (appears at index 2)
By mapping each character to its index in order
, we create a custom comparison key that can be used with Python's built-in sorted()
function. Characters with lower index values will appear earlier in the sorted result.
But what about characters in s
that don't appear in order
? We need to handle these gracefully. The solution assigns them a default priority of 0 using d.get(x, 0)
. This places all "unspecified" characters at the beginning of the result. We could also place them at the end by using a large default value, or maintain their original relative order - the problem allows any placement for these characters.
The beauty of this approach is its simplicity: we transform the custom ordering problem into a standard sorting problem by creating an appropriate key function. The dictionary lookup d[c]
gives us O(1) access to each character's priority, making the overall solution efficient at O(n log n) due to the sorting step.
Learn more about Sorting patterns.
Solution Approach
The implementation uses a dictionary-based mapping combined with Python's sorting capabilities to achieve the custom ordering:
Step 1: Build the Priority Map
d = {c: i for i, c in enumerate(order)}
This creates a dictionary where each character from order
maps to its index position. For example, if order = "cba"
, the dictionary becomes {'c': 0, 'b': 1, 'a': 2}
. This gives us O(1) lookup time for character priorities.
Step 2: Sort with Custom Key
sorted(s, key=lambda x: d.get(x, 0))
The sorted()
function sorts the characters of string s
using a custom key function. For each character x
:
- If
x
exists in the dictionaryd
, use its mapped value (index fromorder
) - If
x
doesn't exist ind
, use the default value 0
This ensures characters are sorted according to their position in order
, while characters not in order
are grouped at the beginning (since they all get priority 0).
Step 3: Join the Result
''.join(sorted(...))
Convert the sorted list of characters back into a string using join()
.
Time Complexity: O(n log n) where n is the length of string s
, dominated by the sorting operation.
Space Complexity: O(m + n) where m is the length of order
(for the dictionary) and n is the length of s
(for the sorted list).
The algorithm elegantly handles all edge cases:
- Characters appearing multiple times in
s
maintain their relative grouping - Characters not in
order
are handled gracefully with the default value - The solution works even if
s
is empty or contains only characters not inorder
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate the solution approach.
Given:
order = "cba"
s = "abcd"
Step 1: Build the Priority Map
We create a dictionary mapping each character in order
to its index:
d = {c: i for i, c in enumerate(order)}
# d = {'c': 0, 'b': 1, 'a': 2}
Step 2: Process Each Character in s
Now let's see how each character in s = "abcd"
gets its sorting priority:
Character | In dictionary? | Priority value | Explanation |
---|---|---|---|
'a' | Yes | 2 | d.get('a', 0) returns d['a'] = 2 |
'b' | Yes | 1 | d.get('b', 0) returns d['b'] = 1 |
'c' | Yes | 0 | d.get('c', 0) returns d['c'] = 0 |
'd' | No | 0 | d.get('d', 0) returns default value 0 |
Step 3: Sort by Priority
The characters with their priorities are:
- 'a' β priority 2
- 'b' β priority 1
- 'c' β priority 0
- 'd' β priority 0
When sorted by priority (ascending order):
- 'c' (priority 0) comes first
- 'd' (priority 0) comes next (same priority as 'c', but 'c' was from
order
) - 'b' (priority 1) comes third
- 'a' (priority 2) comes last
Step 4: Join the Result
The sorted list ['c', 'd', 'b', 'a']
is joined into the final string: "cdba"
Verification:
- β 'c' appears before 'b' (as specified in
order = "cba"
) - β 'c' appears before 'a' (as specified in
order = "cba"
) - β 'b' appears before 'a' (as specified in
order = "cba"
) - β 'd' is placed at the beginning with other non-ordered characters
The final result "cdba"
correctly follows the custom ordering rules!
Solution Implementation
1class Solution:
2 def customSortString(self, order: str, s: str) -> str:
3 # Create a dictionary mapping each character in 'order' to its index/position
4 # This will be used to determine the sorting priority
5 char_to_priority = {char: index for index, char in enumerate(order)}
6
7 # Sort the string 's' based on custom order
8 # Characters in 'order' are sorted by their position in 'order'
9 # Characters not in 'order' get default priority of 0 (will appear first)
10 sorted_chars = sorted(s, key=lambda char: char_to_priority.get(char, 0))
11
12 # Join the sorted characters back into a string
13 return ''.join(sorted_chars)
14
1class Solution {
2 public String customSortString(String order, String s) {
3 // Create an array to store the position/priority of each character
4 // Index represents character (a-z), value represents its position in order string
5 int[] charPriority = new int[26];
6
7 // Assign priority values based on character positions in order string
8 // Characters appearing earlier get lower values (higher priority)
9 for (int i = 0; i < order.length(); i++) {
10 charPriority[order.charAt(i) - 'a'] = i;
11 }
12
13 // Convert string s to a list of characters for sorting
14 List<Character> characterList = new ArrayList<>();
15 for (int i = 0; i < s.length(); i++) {
16 characterList.add(s.charAt(i));
17 }
18
19 // Sort characters based on their priority values
20 // Characters not in order string will have priority 0 and appear first
21 characterList.sort((char1, char2) ->
22 charPriority[char1 - 'a'] - charPriority[char2 - 'a']
23 );
24
25 // Convert sorted character list back to string
26 return characterList.stream()
27 .map(String::valueOf)
28 .collect(Collectors.joining());
29 }
30}
31
1class Solution {
2public:
3 string customSortString(string order, string s) {
4 // Create an array to store the priority/position of each character
5 // Initialize all positions to 0 (characters not in order will have priority 0)
6 int charPriority[26] = {0};
7
8 // Assign priority to each character based on its position in 'order' string
9 // Earlier characters in 'order' get lower priority values (higher precedence)
10 for (int i = 0; i < order.size(); ++i) {
11 charPriority[order[i] - 'a'] = i;
12 }
13
14 // Sort string 's' based on the custom priority defined by 'order'
15 // Characters with lower priority values come first
16 sort(s.begin(), s.end(), [&](char a, char b) {
17 return charPriority[a - 'a'] < charPriority[b - 'a'];
18 });
19
20 return s;
21 }
22};
23
1/**
2 * Sorts string s according to the custom order defined in the order string.
3 * Characters in order appear first in their given order, followed by any remaining characters.
4 *
5 * @param order - The string defining the custom sort order
6 * @param s - The string to be sorted
7 * @returns The sorted string according to the custom order
8 */
9function customSortString(order: string, s: string): string {
10 // Helper function to convert a character to its index (0-25 for 'a'-'z')
11 const charToIndex = (char: string): number => {
12 return char.charCodeAt(0) - 'a'.charCodeAt(0);
13 };
14
15 // Get the length of the order string
16 const orderLength: number = order.length;
17
18 // Initialize priority array for all 26 lowercase letters
19 // Characters not in order get the highest priority value (orderLength)
20 const charPriorities: number[] = new Array(26).fill(orderLength);
21
22 // Assign priorities based on position in order string
23 // Earlier position = lower priority value = higher sorting priority
24 for (let i = 0; i < orderLength; i++) {
25 const currentChar: string = order[i];
26 const index: number = charToIndex(currentChar);
27 charPriorities[index] = i;
28 }
29
30 // Convert string to array, sort by priority, then join back to string
31 const sortedChars: string[] = [...s].sort((charA: string, charB: string) => {
32 const priorityA: number = charPriorities[charToIndex(charA)];
33 const priorityB: number = charPriorities[charToIndex(charB)];
34 return priorityA - priorityB;
35 });
36
37 return sortedChars.join('');
38}
39
Time and Space Complexity
Time Complexity: O(n log n)
where n
is the length of string s
.
- Creating the dictionary
d
fromorder
takesO(m)
time wherem
is the length oforder
- The
sorted()
function uses Timsort algorithm which hasO(n log n)
time complexity for sortingn
characters in strings
- The lambda function
key=lambda x: d.get(x, 0)
is called for each character during sorting, and dictionary lookup isO(1)
- The
''.join()
operation takesO(n)
time to concatenate the sorted characters - Overall:
O(m) + O(n log n) + O(n) = O(n log n)
(assumingn β₯ m
in typical cases)
Space Complexity: O(n + m)
- The dictionary
d
stores at mostm
key-value pairs fromorder
, usingO(m)
space - The
sorted()
function creates a new list containing alln
characters from strings
, usingO(n)
space - The final string created by
''.join()
also usesO(n)
space, but this is the output and typically not counted in auxiliary space - Overall auxiliary space:
O(n + m)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Incorrect Default Value for Characters Not in Order
The Problem:
The current solution uses 0
as the default value for characters not present in the order
string. This places all such characters at the beginning of the result, which might not be the intended behavior. Many would expect these characters to appear at the end instead.
Example:
order = "cba"
,s = "abcd"
- Current output:
"dcba"
(d comes first with priority 0) - Expected by many:
"cbad"
(d comes last)
Solution:
Use a large number (like len(order)
or float('inf')
) as the default value to place unordered characters at the end:
def customSortString(self, order: str, s: str) -> str:
char_to_priority = {char: index for index, char in enumerate(order)}
# Use len(order) to place unordered characters at the end
sorted_chars = sorted(s, key=lambda char: char_to_priority.get(char, len(order)))
return ''.join(sorted_chars)
Pitfall 2: Confusion About Relative Order of Unordered Characters
The Problem:
When multiple characters from s
don't appear in order
, developers might assume these characters should maintain their original relative positions (stable sort), but with the same priority value, their relative order depends on Python's sort stability.
Example:
order = "ab"
,s = "xyabz"
- With default value 0: outputs
"xyzab"
(x, y, z all get priority 0, maintain relative order) - With default value at end: outputs
"abxyz"
(x, y, z all get same high priority)
Solution: If preserving the original order of unordered characters is important, use a two-tier sorting key:
def customSortString(self, order: str, s: str) -> str:
char_to_priority = {char: index for index, char in enumerate(order)}
# Two-tier key: (priority, original_index) to maintain stability
indexed_s = [(char, i) for i, char in enumerate(s)]
sorted_chars = sorted(indexed_s,
key=lambda x: (char_to_priority.get(x[0], len(order)), x[1]))
return ''.join(char for char, _ in sorted_chars)
Pitfall 3: Performance Optimization Opportunity Missed
The Problem:
For cases where order
contains most ASCII characters and s
is very large, the sorting approach has O(n log n) complexity when a counting sort approach could achieve O(n).
Solution: Use a counting/bucket sort approach for better performance:
def customSortString(self, order: str, s: str) -> str:
# Count frequency of each character in s
char_count = {}
for char in s:
char_count[char] = char_count.get(char, 0) + 1
result = []
# Add characters in order
for char in order:
if char in char_count:
result.append(char * char_count[char])
del char_count[char]
# Add remaining characters
for char, count in char_count.items():
result.append(char * count)
return ''.join(result)
This approach has O(n + m) time complexity where n = len(s) and m = len(order).
Which of the following is a good use case for backtracking?
Recommended Readings
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!