Facebook Pixel

451. Sort Characters By Frequency

MediumHash TableStringBucket SortCountingSortingHeap (Priority Queue)
Leetcode Link

Problem Description

You are given a string s consisting of various characters. Your task is to rearrange the characters in the string based on how frequently each character appears, sorting them in decreasing order of frequency.

The frequency of a character means the number of times that character appears in the string. Characters that appear more frequently should come before characters that appear less frequently in the result.

For example:

  • If the input is "tree", the character 'e' appears 2 times while 't' and 'r' each appear 1 time
  • A valid output would be "eert" or "eetr" (since 'e' has the highest frequency, it comes first)

When multiple characters have the same frequency, they can be arranged in any order relative to each other. This means there might be multiple valid answers for the same input.

The solution uses a hash table to count character frequencies, sorts the characters by their counts in descending order, and then builds the result string by repeating each character according to its frequency.

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 know two pieces of information for each character: what the character is and how many times it appears. This naturally leads us to think about using a frequency counter or hash table.

Once we have the frequency of each character, the problem becomes a sorting problem. We want characters with higher frequencies to appear first in our result. This suggests we should sort our character-frequency pairs by frequency in descending order.

The final step comes from understanding what the output should look like. If a character appears n times in the original string, it should also appear n times consecutively in the result (since we're grouping by frequency). For instance, if 'e' appears 3 times, we want "eee" in our result string.

This leads to a three-step approach:

  1. Count how often each character appears using a hash table
  2. Sort these character-frequency pairs by frequency (highest to lowest)
  3. Build the result by repeating each character according to its frequency

The beauty of this solution is its simplicity - we're essentially just counting, sorting, and reconstructing. The Counter class in Python makes the counting trivial, and the sorting with a custom key lambda x: -x[1] (negative to get descending order) handles the ordering requirement. Finally, string multiplication (c * v) elegantly handles repeating each character the correct number of times.

Solution Approach

The implementation follows a straightforward approach using a hash table and sorting:

Step 1: Count Character Frequencies

We use Python's Counter class from the collections module to create a hash table cnt that stores each character and its frequency:

cnt = Counter(s)

For example, if s = "tree", then cnt would be {'t': 1, 'r': 1, 'e': 2}.

Step 2: Sort by Frequency in Descending Order

We need to sort the character-frequency pairs by their frequencies. The cnt.items() gives us pairs like (character, frequency). We sort these pairs using a custom key:

sorted(cnt.items(), key=lambda x: -x[1])
  • lambda x: -x[1] extracts the frequency (second element of each pair) and negates it
  • The negation achieves descending order (since Python's sorted() sorts in ascending order by default)
  • After sorting, we get pairs ordered from highest to lowest frequency

Step 3: Build the Result String

We construct the final string using a generator expression with join():

''.join(c * v for c, v in sorted(...))
  • For each (character, frequency) pair, we multiply the character by its frequency: c * v
  • This creates a string with the character repeated the correct number of times
  • ''.join() concatenates all these repeated character strings into the final result

The entire solution is elegantly compressed into a single line that chains these operations together. The time complexity is O(n log n) due to sorting, where n is the number of unique characters, and the space complexity is O(n) for storing the frequency map.

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 the solution with the input string s = "aabbbc".

Step 1: Count Character Frequencies

First, we create a frequency counter:

cnt = Counter("aabbbc")
# Result: {'a': 2, 'b': 3, 'c': 1}

We now have:

  • 'a' appears 2 times
  • 'b' appears 3 times
  • 'c' appears 1 time

Step 2: Sort by Frequency in Descending Order

Next, we sort these character-frequency pairs by frequency (highest to lowest):

sorted(cnt.items(), key=lambda x: -x[1])
# cnt.items() gives us: [('a', 2), ('b', 3), ('c', 1)]
# After sorting by -frequency: [('b', 3), ('a', 2), ('c', 1)]

The lambda function lambda x: -x[1] takes each pair and returns the negative of its frequency:

  • ('b', 3) → -3
  • ('a', 2) → -2
  • ('c', 1) → -1

Sorting by these negative values gives us descending order by frequency.

Step 3: Build the Result String

Finally, we construct the result by repeating each character according to its frequency:

# For each (character, frequency) pair:
# ('b', 3) → 'b' * 3 = 'bbb'
# ('a', 2) → 'a' * 2 = 'aa'
# ('c', 1) → 'c' * 1 = 'c'

# Join them together:
result = 'bbb' + 'aa' + 'c' = "bbbaac"

The final output is "bbbaac", where characters are arranged by decreasing frequency: 'b' (3 times), then 'a' (2 times), then 'c' (1 time).

Solution Implementation

1from collections import Counter
2
3class Solution:
4    def frequencySort(self, s: str) -> str:
5        # Count the frequency of each character in the string
6        char_frequency = Counter(s)
7      
8        # Sort characters by frequency in descending order
9        # key=lambda x: -x[1] sorts by negative frequency (highest first)
10        sorted_chars = sorted(char_frequency.items(), key=lambda item: -item[1])
11      
12        # Build the result string by repeating each character by its frequency
13        result = ''.join(char * freq for char, freq in sorted_chars)
14      
15        return result
16
1class Solution {
2    public String frequencySort(String s) {
3        // Create a frequency map to count occurrences of each character
4        // Initial capacity of 52 for uppercase and lowercase letters
5        Map<Character, Integer> frequencyMap = new HashMap<>(52);
6      
7        // Count the frequency of each character in the string
8        for (int i = 0; i < s.length(); i++) {
9            char currentChar = s.charAt(i);
10            frequencyMap.merge(currentChar, 1, Integer::sum);
11        }
12      
13        // Create a list of unique characters from the frequency map
14        List<Character> uniqueChars = new ArrayList<>(frequencyMap.keySet());
15      
16        // Sort characters in descending order by their frequency
17        uniqueChars.sort((char1, char2) -> frequencyMap.get(char2) - frequencyMap.get(char1));
18      
19        // Build the result string by appending each character based on its frequency
20        StringBuilder result = new StringBuilder();
21        for (char character : uniqueChars) {
22            int frequency = frequencyMap.get(character);
23            // Append the character 'frequency' times to the result
24            for (int count = 0; count < frequency; count++) {
25                result.append(character);
26            }
27        }
28      
29        // Return the final sorted string
30        return result.toString();
31    }
32}
33
1class Solution {
2public:
3    string frequencySort(string s) {
4        // Count frequency of each character
5        unordered_map<char, int> charFrequency;
6        for (char ch : s) {
7            ++charFrequency[ch];
8        }
9      
10        // Extract all unique characters
11        vector<char> uniqueChars;
12        for (auto& [ch, freq] : charFrequency) {
13            uniqueChars.push_back(ch);
14        }
15      
16        // Sort characters by frequency in descending order
17        sort(uniqueChars.begin(), uniqueChars.end(), [&](char a, char b) {
18            return charFrequency[a] > charFrequency[b];
19        });
20      
21        // Build result string by appending each character according to its frequency
22        string result;
23        for (char ch : uniqueChars) {
24            result += string(charFrequency[ch], ch);
25        }
26      
27        return result;
28    }
29};
30
1/**
2 * Sorts characters in a string by their frequency in descending order
3 * @param s - The input string to sort
4 * @returns A string with characters sorted by frequency (highest to lowest)
5 */
6function frequencySort(s: string): string {
7    // Create a map to store character frequencies
8    const characterFrequencyMap: Map<string, number> = new Map();
9  
10    // Count the frequency of each character in the string
11    for (const character of s) {
12        characterFrequencyMap.set(
13            character, 
14            (characterFrequencyMap.get(character) || 0) + 1
15        );
16    }
17  
18    // Get all unique characters and sort them by frequency in descending order
19    const sortedCharacters: string[] = Array.from(characterFrequencyMap.keys()).sort(
20        (charA: string, charB: string) => {
21            const frequencyA: number = characterFrequencyMap.get(charA)!;
22            const frequencyB: number = characterFrequencyMap.get(charB)!;
23            return frequencyB - frequencyA;
24        }
25    );
26  
27    // Build the result string by repeating each character according to its frequency
28    const resultArray: string[] = [];
29    for (const character of sortedCharacters) {
30        const frequency: number = characterFrequencyMap.get(character)!;
31        resultArray.push(character.repeat(frequency));
32    }
33  
34    // Join all repeated characters into the final result string
35    return resultArray.join('');
36}
37

Time and Space Complexity

Time Complexity: O(n + k × log k)

  • Creating the Counter takes O(n) time, where n is the length of string s, as it needs to iterate through each character once
  • The sorted() function sorts k distinct character-frequency pairs, which takes O(k × log k) time
  • The join() operation iterates through all k sorted items and concatenates characters based on their frequencies. Since the total number of characters written is n, this takes O(n) time
  • Overall: O(n) + O(k × log k) + O(n) = O(n + k × log k)

Space Complexity: O(n + k)

  • The Counter object stores k distinct characters and their frequencies, requiring O(k) space
  • The sorted list of tuples also contains k items, requiring O(k) space
  • The final result string has length n, requiring O(n) space
  • Overall: O(k) + O(k) + O(n) = O(n + k)

Where n is the length of the input string s and k is the number of distinct characters in the string.

Common Pitfalls

1. Incorrect Handling of Characters with Same Frequency

A common mistake is assuming that characters with the same frequency must maintain a specific order (like alphabetical order). This can lead to unnecessary complexity or incorrect validation of results.

Pitfall Example:

# Incorrect assumption - trying to maintain alphabetical order for same frequencies
sorted_chars = sorted(char_frequency.items(), key=lambda x: (-x[1], x[0]))

Why it's wrong: The problem explicitly states that characters with the same frequency can appear in any order. Adding secondary sorting criteria when not required increases complexity without benefit.

Correct Approach:

# Simple and correct - only sort by frequency
sorted_chars = sorted(char_frequency.items(), key=lambda x: -x[1])

2. Using Default Ascending Sort Without Negation

Developers might forget to sort in descending order or use incorrect methods to achieve it.

Pitfall Example:

# Wrong - sorts in ascending order (lowest frequency first)
sorted_chars = sorted(char_frequency.items(), key=lambda x: x[1])

Solution: Either use negation or the reverse parameter:

# Option 1: Negate the frequency
sorted_chars = sorted(char_frequency.items(), key=lambda x: -x[1])

# Option 2: Use reverse parameter
sorted_chars = sorted(char_frequency.items(), key=lambda x: x[1], reverse=True)

3. Inefficient String Concatenation in Loops

Building the result string using repeated concatenation in a loop is inefficient in Python.

Pitfall Example:

result = ""
for char, freq in sorted_chars:
    for _ in range(freq):
        result += char  # Inefficient - creates new string object each time

Why it's inefficient: Strings are immutable in Python, so each += operation creates a new string object, leading to O(n²) time complexity for string building.

Correct Approach: Use join() with a generator or list comprehension:

# Efficient - builds string in one operation
result = ''.join(char * freq for char, freq in sorted_chars)

4. Not Handling Edge Cases

Failing to consider empty strings or strings with only one character.

Pitfall Example:

def frequencySort(self, s: str) -> str:
    # No validation - might fail on edge cases
    char_frequency = Counter(s)
    sorted_chars = sorted(char_frequency.items(), key=lambda x: -x[1])
    return ''.join(char * freq for char, freq in sorted_chars)

Robust Solution:

def frequencySort(self, s: str) -> str:
    # Handle empty string
    if not s:
        return ""
  
    # Handle single character
    if len(s) == 1:
        return s
  
    # Regular processing
    char_frequency = Counter(s)
    sorted_chars = sorted(char_frequency.items(), key=lambda x: -x[1])
    return ''.join(char * freq for char, freq in sorted_chars)

Note: While the original solution handles these cases correctly due to Python's built-in behavior, explicit handling makes the code more readable and maintainable.

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

What is an advantages of top-down dynamic programming vs bottom-up dynamic programming?


Recommended Readings

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

Load More