451. Sort Characters By Frequency
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.
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:
- Count how often each character appears using a hash table
- Sort these character-frequency pairs by frequency (highest to lowest)
- 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 EvaluatorExample 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, wheren
is the length of strings
, as it needs to iterate through each character once - The
sorted()
function sortsk
distinct character-frequency pairs, which takesO(k × log k)
time - The
join()
operation iterates through allk
sorted items and concatenates characters based on their frequencies. Since the total number of characters written isn
, this takesO(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, requiringO(k)
space - The sorted list of tuples also contains
k
items, requiringO(k)
space - The final result string has length
n
, requiringO(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.
What is an advantages of top-down dynamic programming vs bottom-up dynamic programming?
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
https assets algo monster cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue is a data structure
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
Want a Structured Path to Master System Design Too? Don’t Miss This!