Facebook Pixel

791. Custom Sort String

MediumHash TableStringSorting
Leetcode Link

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" and s = "abcd", then "cbad" would be a valid result because:
    • c comes before b in order, so c comes before b in the result
    • c comes before a in order, so c comes before a in the result
    • b comes before a in order, so b comes before a in the result
    • d is not in order, 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 dictionary d, use its mapped value (index from order)
  • If x doesn't exist in d, 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 in order

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example 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:

CharacterIn dictionary?Priority valueExplanation
'a'Yes2d.get('a', 0) returns d['a'] = 2
'b'Yes1d.get('b', 0) returns d['b'] = 1
'c'Yes0d.get('c', 0) returns d['c'] = 0
'd'No0d.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):

  1. 'c' (priority 0) comes first
  2. 'd' (priority 0) comes next (same priority as 'c', but 'c' was from order)
  3. 'b' (priority 1) comes third
  4. '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 from order takes O(m) time where m is the length of order
  • The sorted() function uses Timsort algorithm which has O(n log n) time complexity for sorting n characters in string s
  • The lambda function key=lambda x: d.get(x, 0) is called for each character during sorting, and dictionary lookup is O(1)
  • The ''.join() operation takes O(n) time to concatenate the sorted characters
  • Overall: O(m) + O(n log n) + O(n) = O(n log n) (assuming n β‰₯ m in typical cases)

Space Complexity: O(n + m)

  • The dictionary d stores at most m key-value pairs from order, using O(m) space
  • The sorted() function creates a new list containing all n characters from string s, using O(n) space
  • The final string created by ''.join() also uses O(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).

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which of the following is a good use case for backtracking?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More