451. Sort Characters By Frequency
Problem Description
The problem requires us to tackle a string sorting task based on a non-standard criterion: the frequency of each character in the string. Specifically, the goal is to reorder the string so that the characters that occur most frequently are placed first. If two characters have the same frequency, they can be in any order with respect to each other. The final output must be a string where the sorted order reflects these frequency criteria.
Intuition
The intuitive approach to this problem involves counting how often each character appears in the string, then sorting the characters based on these counts. This is typically a two-step process:
-
Count the occurrences: We need to go through the string and count the occurrences of each character. This can be done efficiently by using a hash table or a counter data structure where each character is a key, and its count is the value.
-
Sort based on counts: Once we have the counts, the next step is to sort the characters by these counts in descending order. We want the characters with higher counts to come first.
With these counts, we can construct a new string. We do this by iterating over each unique character, repeating the character by its count (since sorting by frequency means if a character appears ( n ) times, it should also appear ( n ) times consecutively in the final string), and concatenating these repetitions to form the final string.
In the provided solution:
- The
Counter
from the collections module is used to count occurrences of each character. - The
sorted()
function sorts the items in the counter by their counts (values), with the sort being in descending order because of the negative sign in the sort key-x[1]
. - The sorted items are then concatenated to create the final string through a string join operation, which combines the characters multiplied by their frequencies.
This method is consistent with the requirements and efficiently achieves the sorting based on frequency.
Learn more about Sorting and Heap (Priority Queue) patterns.
Solution Approach
The solution makes use of a few key concepts in Python to address the problem:
-
Counter Data Structure: The
Counter
class from thecollections
module is perfect for counting the frequency of characters because it automatically builds a hash map (dictionary) where characters are keys and their counts are values.cnt = Counter(s)
Here,
s
is the input string, andcnt
becomes aCounter
object holding counts of each character. -
Sorting by Frequency: The
sorted()
function is used to sort the characters based on their frequency.sorted(cnt.items(), key=lambda x: -x[1])
cnt.items()
provides a sequence of(character, count)
pairs. Thekey
argument tosorted()
specifies that sorting should be based on the count, which is the second item in each pair (x[1]
). The negative sign ensures that the sorting is in decreasing order of frequency. -
String Joining and Character Multiplication: Python's expressive syntax allows us to multiply a string by an integer to repeat it, and the
join()
method of a string allows us to concatenate an iterable of strings:return ''.join(c * v for c, v in sorted(cnt.items(), key=lambda x: -x[1]))
For each character
c
and its countv
,c * v
creates a string wherec
is repeatedv
times. Thejoin()
method is used to concatenate all these strings together without any separator (''
), creating the final sorted string.
These steps are combined into a concise one-liner inside the frequencySort
method of the Solution
class. This is efficient because it leverages Python's built-in data structures and functions that are implemented in C under the hood, thus being quite fast.
class Solution:
def frequencySort(self, s: str) -> str:
cnt = Counter(s)
return ''.join(c * v for c, v in sorted(cnt.items(), key=lambda x: -x[1]))
The approach works well and guarantees that the most frequent characters will be placed at the beginning of the resulting string, while less frequent characters will follow, adhering to the problem's constraints.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's run through a small example to illustrate how the solution approach works. Suppose our input string is s = "tree"
.
-
Count the occurrences of each character: We use the
Counter
class to count the characters.from collections import Counter cnt = Counter("tree") # cnt is now Counter({'t': 1, 'r': 1, 'e': 2})
In the string "tree", the character 'e' appears twice, while 't' and 'r' each appear once.
-
Sort characters by frequency: We then use the
sorted()
function to sort these characters by their frequency in descending order.sorted_characters = sorted(cnt.items(), key=lambda x: -x[1]) # sorted_characters is now [('e', 2), ('t', 1), ('r', 1)]
Here, we use a lambda function as the key to sort by the counts—the negative sign ensures it is in descending order.
-
Construct the new string based on frequency: Finally, we iterate over the sorted character-count pairs and repeat each character by its frequency, joining them to form the final result.
result_string = ''.join(c * v for c, v in sorted_characters) # result_string is "eett" or "eetr" or "tree" or "ttee", etc.
Since 'e' has the highest frequency, it comes first. 't' and 'r' have the same frequency, so their order with respect to each other does not matter in the final output. The result can be "eett", "eetr", "tree", or "ttee" because the order of characters with the same frequency is not specified.
Putting all this within the class method frequencySort
would look like this:
class Solution:
def frequencySort(self, s: str) -> str:
cnt = Counter(s)
return ''.join(c * v for c, v in sorted(cnt.items(), key=lambda x: -x[1]))
By applying this method to our example:
solution = Solution()
print(solution.frequencySort("tree"))
We will get a string that has 'e' characters first because they have the highest frequency, followed by 't' and 'r' in any order, which may result in one of the possible outcomes such as "eett", "eetr", "tree", or "ttee".
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 input string
6 char_frequency = Counter(s)
7
8 # Sort the characters based on frequency in descending order
9 sorted_characters = sorted(char_frequency.items(), key=lambda item: -item[1])
10
11 # Create a string with characters repeated by their frequency
12 frequency_sorted_string = ''.join(character * frequency for character, frequency in sorted_characters)
13
14 return frequency_sorted_string
15
16# Example usage:
17# sol = Solution()
18# result = sol.frequencySort("tree")
19# print(result) # Outputs a string with characters sorted by frequency, e.g. "eetr"
20
1import java.util.*;
2
3class Solution {
4 public String frequencySort(String s) {
5 // Initialize a hash map to store frequency of each character
6 Map<Character, Integer> frequencyMap = new HashMap<>(52);
7
8 // Loop through all the characters in the string to fill the frequency map
9 for (int i = 0; i < s.length(); ++i) {
10 // Merge the current character into the map, increasing its count by 1
11 frequencyMap.merge(s.charAt(i), 1, Integer::sum);
12 }
13
14 // Create a list to store the characters (for sorting purposes)
15 List<Character> characters = new ArrayList<>(frequencyMap.keySet());
16
17 // Sort the character list based on their frequencies in descending order
18 characters.sort((a, b) -> frequencyMap.get(b) - frequencyMap.get(a));
19
20 // Use StringBuilder to build the result string
21 StringBuilder sortedString = new StringBuilder();
22
23 // Loop through the sorted list of characters
24 for (char c : characters) {
25 // Append each character to the StringBuilder based on its frequency
26 for (int frequency = frequencyMap.get(c); frequency > 0; --frequency) {
27 sortedString.append(c);
28 }
29 }
30
31 // Return the sorted string
32 return sortedString.toString();
33 }
34}
35
1#include <string>
2#include <unordered_map>
3#include <vector>
4#include <algorithm>
5
6class Solution {
7public:
8 // Function to sort characters by frequency of appearance in a string
9 string frequencySort(string s) {
10 // Create a hash map to store the frequency of appearance of each character
11 unordered_map<char, int> frequencyMap;
12 // Calculate the frequency of each character in the string
13 for (char ch : s) {
14 ++frequencyMap[ch];
15 }
16
17 // Create a vector to store unique characters
18 vector<char> uniqueChars;
19 // Populate the vector with the keys from the frequencyMap
20 for (auto& keyValue : frequencyMap) {
21 uniqueChars.push_back(keyValue.first);
22 }
23
24 // Sort the unique characters based on their frequency
25 sort(uniqueChars.begin(), uniqueChars.end(), [&](char a, char b) {
26 return frequencyMap[a] > frequencyMap[b];
27 });
28
29 // Create a result string to store the sorted characters by frequency
30 string result;
31 // Go through each character and append it to the result string, multiplied by its frequency
32 for (char ch : uniqueChars) {
33 result += string(frequencyMap[ch], ch);
34 }
35
36 // Return the result string
37 return result;
38 }
39};
40
1// Function to sort the characters in a string by frequency of appearance in descending order.
2function frequencySort(s: string): string {
3 // Create a map to hold character frequencies.
4 const charFrequencyMap: Map<string, number> = new Map();
5
6 // Iterate over each character in the input string.
7 for (const char of s) {
8 // Update the frequency count for each character.
9 charFrequencyMap.set(char, (charFrequencyMap.get(char) || 0) + 1);
10 }
11
12 // Convert map keys to an array, sort the array by frequency in descending order.
13 const sortedCharacters = Array.from(charFrequencyMap.keys()).sort(
14 (charA, charB) => charFrequencyMap.get(charB)! - charFrequencyMap.get(charA)!
15 );
16
17 // Initialize an array to hold the sorted characters by frequency.
18 const sortedArray: string[] = [];
19
20 // Build a string for each character, repeated by its frequency.
21 for (const char of sortedCharacters) {
22 sortedArray.push(char.repeat(charFrequencyMap.get(char)!));
23 }
24
25 // Join the array of strings into a single string and return it.
26 return sortedArray.join('');
27}
28
Time and Space Complexity
Time Complexity:
The time complexity of the provided code can be analyzed as follows:
-
Counting the frequency of each character - The
Counter
from thecollections
module iterates through the strings
once to count the frequency of each character. This operation has a time complexity ofO(n)
, wheren
is the length of the strings
. -
Sorting the counted characters - The
sorted
function is used to sort the items in the counter based on their frequency (the value in the key-value pair). Sorting in python is typically implemented with the Timsort algorithm, which has a time complexity ofO(m log m)
, wherem
is the number of unique characters in the strings
.
Overall, the dominating factor is the sort operation, so the total time complexity is O(m log m + n)
. However, since m
can be at most n
in cases where all characters are unique, the time complexity is often described as O(n log n)
for practical worst-case scenarios.
Space Complexity:
The space complexity of the code is analyzed as follows:
-
Counter dictionary - The
Counter
constructs a dictionary withm
entries, wherem
is the number of unique characters in the strings
. This space usage isO(m)
. -
Sorted list - The
sorted
function generates a list of tuples which is essentially the items of the counter sorted based on their frequency. This also takesO(m)
space. -
Output string - The output string is formed by joining individual characters multiplied by their frequency. The length of the resulting string is equal to
n
, the length of the input string. Hence, the space required for the output string isO(n)
.
Since m
can be at most n
, the overall space complexity is O(n)
considering the storage for the output string and the data structures used for counting and sorting.
Learn more about how to find time and space complexity quickly using problem constraints.
Consider the classic dynamic programming of longest increasing subsequence:
Find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order.
For example, the length of LIS for [50, 3, 10, 7, 40, 80]
is 4
and LIS is
[3, 7, 40, 80]
.
What is the recurrence relation?
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 algomonster s3 us east 2 amazonaws com 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
LeetCode 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 we
Want a Structured Path to Master System Design Too? Don’t Miss This!