2268. Minimum Number of Keypresses π
Problem Description
You have a keypad with 9 buttons numbered from 1 to 9. You need to map all 26 lowercase English letters to these buttons with the following rules:
- Every lowercase letter from 'a' to 'z' must be mapped to exactly one button
- Each button can have at most 3 characters mapped to it
- Once you decide the mapping, it cannot be changed
To type a character using the keypad:
- Press the button once to type the first character mapped to that button
- Press the button twice to type the second character mapped to that button
- Press the button three times to type the third character mapped to that button
Given a string s
, you need to find the minimum number of total key presses required to type the entire string. You have the freedom to choose which characters are mapped to which buttons (within the constraints) to minimize the total key presses.
For example, if button 2 has characters 'a', 'b', 'c' mapped to it:
- To type 'a': press button 2 once (1 keypress)
- To type 'b': press button 2 twice (2 keypresses)
- To type 'c': press button 2 three times (3 keypresses)
The strategy is to map the most frequently occurring characters in the string to positions that require fewer key presses. Since we have 9 buttons and each can hold up to 3 characters, we can map:
- The 9 most frequent characters to the first position of each button (1 keypress each)
- The next 9 most frequent characters to the second position of each button (2 keypresses each)
- The remaining 8 characters to the third position of buttons (3 keypresses each)
Intuition
The key insight is that we want to minimize the total number of key presses, and we have complete control over how we map characters to buttons. This is a classic greedy optimization problem.
Think about it this way: if a character appears 100 times in the string and another appears only once, which one should require fewer key presses? Obviously, the one that appears 100 times should be easier to type (require fewer presses).
Since each button position requires a different number of presses:
- Position 1: 1 press
- Position 2: 2 presses
- Position 3: 3 presses
We should assign the most frequent characters to positions requiring fewer presses. With 9 buttons available:
- The 9 most frequent characters should go in position 1 of each button (1 press each)
- The next 9 most frequent characters should go in position 2 of each button (2 presses each)
- The remaining 8 characters (26 - 18 = 8) should go in position 3 of some buttons (3 presses each)
This greedy approach works because there's no interdependency between character assignments - placing a frequent character in a good position doesn't prevent us from placing another frequent character in another good position. We simply need to count character frequencies, sort them in descending order, and assign them to positions in order of increasing cost.
The total cost is then calculated as: frequency Γ keypresses_required
for each character, where keypresses_required
increases by 1 after every 9 characters are assigned.
Solution Approach
The implementation follows the greedy strategy we identified:
-
Count character frequencies: Use a
Counter
to count how many times each character appears in the strings
. This gives us a dictionary where keys are characters and values are their frequencies. -
Sort frequencies in descending order: Extract just the frequency values from the counter and sort them in descending order using
sorted(cnt.values(), reverse=True)
. We don't need to track which character has which frequency since we only care about the total key presses. -
Calculate total key presses:
- Initialize
ans = 0
to track the total key presses andk = 1
to track the current number of presses required per character - Iterate through the sorted frequencies with an index starting from 1
- For each frequency
x
, addk * x
to the answer (frequency Γ keypresses_required) - Every 9 characters (when
i % 9 == 0
), incrementk
by 1 since we've filled all 9 buttons at the current level and need to move to the next position
- Initialize
The algorithm works as follows:
- Characters 1-9 (most frequent) get assigned to position 1 of buttons 1-9, requiring
1 * frequency
presses each - Characters 10-18 get assigned to position 2 of buttons 1-9, requiring
2 * frequency
presses each - Characters 19-26 get assigned to position 3 of buttons 1-8, requiring
3 * frequency
presses each
Time complexity: O(n + m log m)
where n
is the length of string s
and m
is the number of unique characters (at most 26)
Space complexity: O(m)
for storing the character counts
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example with the string s = "aabbccdddeee"
.
Step 1: Count character frequencies
- 'a' appears 2 times
- 'b' appears 2 times
- 'c' appears 2 times
- 'd' appears 3 times
- 'e' appears 3 times
Step 2: Sort frequencies in descending order Sorted frequencies: [3, 3, 2, 2, 2]
Step 3: Assign positions and calculate key presses
We have 9 buttons available, each with 3 positions:
- Position 1 (1 press): Can hold 9 characters
- Position 2 (2 presses): Can hold 9 characters
- Position 3 (3 presses): Can hold 8 characters
Let's process each frequency with its position cost:
Frequency | Position | Presses Required | Total Presses |
---|---|---|---|
3 (1st char) | Button 1, Pos 1 | 1 | 3 Γ 1 = 3 |
3 (2nd char) | Button 2, Pos 1 | 1 | 3 Γ 1 = 3 |
2 (3rd char) | Button 3, Pos 1 | 1 | 2 Γ 1 = 2 |
2 (4th char) | Button 4, Pos 1 | 1 | 2 Γ 1 = 2 |
2 (5th char) | Button 5, Pos 1 | 1 | 2 Γ 1 = 2 |
Total key presses = 3 + 3 + 2 + 2 + 2 = 12
The algorithm assigns:
- The two most frequent characters (appearing 3 times each) to position 1 of buttons 1 and 2
- The three next frequent characters (appearing 2 times each) to position 1 of buttons 3, 4, and 5
Since we only have 5 unique characters and 9 position-1 slots available, all characters get the optimal 1-press positions. If we had more than 9 unique characters, characters 10-18 would require 2 presses each, and characters 19-26 would require 3 presses each.
The greedy approach ensures we minimize the total by always assigning the most frequent characters to the positions requiring the fewest presses.
Solution Implementation
1from collections import Counter
2
3class Solution:
4 def minimumKeypresses(self, s: str) -> int:
5 # Count frequency of each character in the string
6 char_frequency = Counter(s)
7
8 # Initialize total keypresses and current press count per character
9 total_keypresses = 0
10 presses_per_char = 1
11
12 # Sort frequencies in descending order and iterate with index
13 for index, frequency in enumerate(sorted(char_frequency.values(), reverse=True), 1):
14 # Add keypresses for this character (frequency * presses needed)
15 total_keypresses += presses_per_char * frequency
16
17 # After every 9 characters, increment the number of presses needed
18 # (9 keys available on keypad, excluding 0 and 1)
19 if index % 9 == 0:
20 presses_per_char += 1
21
22 return total_keypresses
23
1class Solution {
2 public int minimumKeypresses(String s) {
3 // Array to store frequency count of each letter (a-z)
4 int[] frequency = new int[26];
5
6 // Count the frequency of each character in the string
7 for (int i = 0; i < s.length(); i++) {
8 frequency[s.charAt(i) - 'a']++;
9 }
10
11 // Sort frequencies in ascending order
12 Arrays.sort(frequency);
13
14 int totalKeypresses = 0;
15 int keypressesPerChar = 1; // Number of keypresses needed for current character
16
17 // Process frequencies from highest to lowest (reverse order after sorting)
18 for (int i = 1; i <= 26; i++) {
19 // Multiply frequency by number of keypresses needed
20 // frequency[26 - i] gives us frequencies in descending order
21 totalKeypresses += keypressesPerChar * frequency[26 - i];
22
23 // Every 9 characters, we need one more keypress
24 // (since a phone keypad has 9 keys with letters)
25 if (i % 9 == 0) {
26 keypressesPerChar++;
27 }
28 }
29
30 return totalKeypresses;
31 }
32}
33
1class Solution {
2public:
3 int minimumKeypresses(string s) {
4 // Array to store frequency of each letter (a-z)
5 int frequency[26]{};
6
7 // Count frequency of each character in the string
8 for (char& c : s) {
9 ++frequency[c - 'a'];
10 }
11
12 // Sort frequencies in descending order to assign most frequent letters first
13 sort(begin(frequency), end(frequency), greater<int>());
14
15 int totalKeypresses = 0;
16 int keypressesPerChar = 1; // Number of key presses needed for current character
17
18 // Assign characters to keys (9 keys available, like phone keypad)
19 for (int i = 1; i <= 26; ++i) {
20 // Add keypresses for this character (frequency * keypresses needed)
21 totalKeypresses += keypressesPerChar * frequency[i - 1];
22
23 // After every 9 characters, increment keypresses needed
24 // (since we've used all 9 keys and need to start second position)
25 if (i % 9 == 0) {
26 ++keypressesPerChar;
27 }
28 }
29
30 return totalKeypresses;
31 }
32};
33
1/**
2 * Calculates the minimum number of keypresses needed to type a string
3 * on a phone keypad where letters can be assigned to keys optimally.
4 * @param s - The input string to be typed
5 * @returns The minimum number of keypresses required
6 */
7function minimumKeypresses(s: string): number {
8 // Count frequency of each letter (assuming lowercase a-z)
9 const letterFrequencies: number[] = Array(26).fill(0);
10 const ASCII_CODE_OF_A = 'a'.charCodeAt(0);
11
12 // Count occurrences of each character in the string
13 for (const char of s) {
14 const letterIndex = char.charCodeAt(0) - ASCII_CODE_OF_A;
15 letterFrequencies[letterIndex]++;
16 }
17
18 // Sort frequencies in descending order to assign most frequent letters
19 // to positions requiring fewer keypresses
20 letterFrequencies.sort((a, b) => b - a);
21
22 let totalKeypresses = 0;
23 let keypressesPerLetter = 1; // Number of presses needed for current position
24
25 // Assign letters to keypad positions
26 // 9 keys available, each can hold multiple letters
27 // First 9 letters need 1 press, next 9 need 2 presses, remaining need 3 presses
28 for (let i = 1; i <= 26; i++) {
29 totalKeypresses += keypressesPerLetter * letterFrequencies[i - 1];
30
31 // After every 9 letters, increase the number of keypresses needed
32 if (i % 9 === 0) {
33 keypressesPerLetter++;
34 }
35 }
36
37 return totalKeypresses;
38}
39
Time and Space Complexity
Time Complexity: O(n + |Ξ£| Γ log |Ξ£|)
The time complexity consists of two main parts:
O(n)
for counting the frequency of each character in the string usingCounter(s)
, wheren
is the length of the stringO(|Ξ£| Γ log |Ξ£|)
for sorting the frequency values, where|Ξ£|
represents the number of distinct characters in the string
Since the problem deals with lowercase letters, |Ξ£| β€ 26
. The sorting operation dominates when the string has many distinct characters, while the counting operation dominates for very long strings with few distinct characters.
Space Complexity: O(|Ξ£|)
The space complexity is determined by:
O(|Ξ£|)
for the Counter dictionary that stores the frequency of each distinct characterO(|Ξ£|)
for the sorted list of frequency values
Both structures are bounded by the number of distinct characters in the string, which is at most 26 for lowercase letters, giving us an overall space complexity of O(|Ξ£|)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Misunderstanding the Keypad Constraint
Issue: A common mistake is assuming we have 10 buttons (0-9) like a standard phone keypad, when the problem explicitly states we have 9 buttons numbered 1-9. This leads to incorrect calculations where developers use modulo 10 instead of modulo 9.
Incorrect Implementation:
def minimumKeypresses(self, s: str) -> int:
char_frequency = Counter(s)
total_keypresses = 0
presses_per_char = 1
for index, frequency in enumerate(sorted(char_frequency.values(), reverse=True), 1):
total_keypresses += presses_per_char * frequency
# WRONG: Using 10 instead of 9
if index % 10 == 0:
presses_per_char += 1
return total_keypresses
Solution: Always use 9 as the modulo value since we have exactly 9 buttons available.
Pitfall 2: Incorrect Index Handling
Issue: Starting the enumeration from 0 instead of 1 causes the press count to increment at the wrong positions. This happens when developers forget to specify the start parameter in enumerate()
.
Incorrect Implementation:
def minimumKeypresses(self, s: str) -> int:
char_frequency = Counter(s)
total_keypresses = 0
presses_per_char = 1
# WRONG: Starting from index 0
for index, frequency in enumerate(sorted(char_frequency.values(), reverse=True)):
total_keypresses += presses_per_char * frequency
# This will increment after 9th, 18th character instead of after 9th, 18th
if (index + 1) % 9 == 0:
presses_per_char += 1
return total_keypresses
Solution: Use enumerate(sorted_frequencies, 1)
to start counting from 1, or adjust the modulo check accordingly if starting from 0.
Pitfall 3: Attempting to Track Character-to-Button Mapping
Issue: Some developers try to maintain the actual mapping of characters to buttons, which unnecessarily complicates the solution and doesn't affect the final answer.
Overcomplicated Implementation:
def minimumKeypresses(self, s: str) -> int:
char_frequency = Counter(s)
button_mapping = {i: [] for i in range(1, 10)} # UNNECESSARY
sorted_chars = sorted(char_frequency.items(), key=lambda x: x[1], reverse=True)
button_num = 1
position = 0
total_keypresses = 0
for char, freq in sorted_chars:
button_mapping[button_num].append(char) # UNNECESSARY
total_keypresses += (position + 1) * freq
button_num += 1
if button_num > 9:
button_num = 1
position += 1
return total_keypresses
Solution: Focus only on frequencies and their positions. The actual character-to-button assignment doesn't matter for calculating the minimum keypresses - only the frequencies and their optimal positioning matter.
Which of the following shows the order of node visit in a Breadth-first Search?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
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
Want a Structured Path to Master System Design Too? Donβt Miss This!