3016. Minimum Number of Pushes to Type Word II
Problem Description
In this problem, we are given a string word
that is comprised of lowercase English letters. The task revolves around the concept that each number key on a telephone keypad can be mapped to a group of distinct lowercase English letters, similar to old feature phones where you would press a number key multiple times to type a letter.
However, the problem allows us to 'remap' the keys numbered 2
through 9
to whatever groups of letters we want. A key point is that any letter can only map to a single key, but a key can have any number of letters assigned to it, and these collections of letters assigned to each key must be distinct from each other.
The ultimate goal is to find the most efficient way to type the given word by remapping these keys in such a way that the total number of key presses is minimized.
Let's consider an example. If the word is "abc"
, and if we map "a"
, "b"
, and "c"
all to the number key 2
, then we'd press 2
one time to type "a"
, two times to type "b"
, and three times to type "c"
, resulting in a total of 1 + 2 + 3 = 6
presses. The problem asks us to minimize such presses across the whole word by an intelligent mapping of letters to keys.
Intuition
To solve this problem, we follow a greedy algorithm approach combined with sorting. The main intuition behind the solution is that we want to minimize the number of times we press the keys to type the most frequent letters in the word. To achieve this, we analyze the frequency of each letter in the word since the more frequently a letter occurs, the more we can save by minimizing its required key presses.
Firstly, we count the occurrences of each letter using a Counter
from the collections
module in Python. This gives us the frequency of every distinct letter in the word.
We then sort these frequencies in descending order because we want to assign the highest frequencies to the least number of presses. We map the most frequent letters to single press keys, the next set of most frequent letters to two press keys, and so on. Since there are 8 number keys (2
to 9
) available to map, we group every 8 letters together, assigning each group consecutively higher number of key presses.
To calculate the number of presses for the i-th most frequent letter, we divide its rank i
(starting from 0
) by 8
because there are eight keys that the letters could be assigned to. We then take the integer division result (which represents the group number), add 1
to get the actual number of presses for the group, and then multiply it by the frequency count of the letter. Summing this value across all the letters gives us the total number of key presses required after an optimal remapping of keys.
By following this strategy, we ensure that the most frequent letters in the word are the quickest to type, thus minimizing the total number of presses.
Solution Approach
The implementation of the provided solution uses a combination of a greedy algorithm, sorting, and a counting mechanism to solve the problem efficiently.
Here's a step-by-step breakdown of the implementation process:
-
Counting the Letter Frequencies:
- The solution begins by using a
Counter
from Python'scollections
module to count the frequency of each letter within theword
. - The
Counter
data structure automatically creates a hash table (dictionary-like structure) where the keys are the distinct letters, and the values are the counts of how many times each letter appears in theword
.
- The solution begins by using a
-
Sorting the Frequencies in Descending Order:
- The next step is to sort the counted letter frequencies in descending order.
- This is done because we want to allocate the most frequently occurring letters to the least number of key presses, following the greedy strategy.
- Sorting is typically achieved through comparison-based algorithms like quicksort or mergesort, which ensure an efficient sorting process—most Python implementations use a variant of Timsort, a hybrid sorting algorithm derived from merge sort and insertion sort.
-
Assigning Letters to Keys:
- Once sorted, the letters are virtually grouped into sets of 8 because we have 8 keys (
2
to9
) that can be remapped to any set of letters. - Starting from the most frequent letter (at the beginning of the sorted count values), each letter is assigned a number of key presses that corresponds to the key it would be mapped to.
- The calculation for the number of presses required for the
i
-th letter (starting index at0
) is:- Divide the letter's rank
i
by8
since we have 8 keys, take the integer quotient which represents the group number. - Add
1
to translate the group number to the number of presses (since group0
would be a single press). - Multiply this result by the frequency of the letter, to find out the total presses needed for that letter.
- Divide the letter's rank
- This is repeated for each letter using a loop.
- Once sorted, the letters are virtually grouped into sets of 8 because we have 8 keys (
-
Calculating the Total Minimum Presses:
- As the last step, each individual letter's presses are added together to yield the total minimum number of key presses required to type the entire
word
after the optimal remapping.
- As the last step, each individual letter's presses are added together to yield the total minimum number of key presses required to type the entire
Using this approach, the Solution
class's minimumPushes
method ensures we arrive at the minimum number of pushes needed to type the word after remapping the keys. The algorithm is efficient because it leverages frequency counts and sorting to minimize the total number of presses in a very direct way, well-suited to the constraints of the problem.
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 go through an example to illustrate the solution approach. Suppose our given word
is "aaaaaaaaaaabbbccc"
.
Step 1: Counting the Letter Frequencies:
We use the Counter
to count the frequency of each letter.
a
: 11 timesb
: 3 timesc
: 3 times
Step 2: Sorting the Frequencies in Descending Order: We sort the letter frequencies:
a
: 11 timesb
: 3 timesc
: 3 times
Since a
is the most frequent, followed by b
and c
(which have equal frequency), we keep this order.
Step 3: Assigning Letters to Keys: We have 8 keys, so let's distribute the letters.
-
The letter
a
gets the first key (Let's assume2
), which needs just 1 press pera
. Sincea
appears 11 times, we have a total of11 * 1 = 11
presses fora
. -
The letters
b
andc
are next in frequency, and they get the second key (Let's assume3
).b
requires 2 presses, and we have 3b
s making3 * 2 = 6
presses forb
. The same calculation applies toc
, resulting in another6
presses.
Step 4: Calculating the Total Minimum Presses: We add up the total number of presses:
a
contribution: 11 pressesb
contribution: 6 pressesc
contribution: 6 presses
So the total minimum number of key presses is 11 + 6 + 6 = 23
presses.
Therefore, by remapping the keys as described, it takes a minimum of 23 presses to type "aaaaaaaaaaabbbccc"
using a telephone keyboard.
Solution Implementation
1from collections import Counter
2
3class Solution:
4 def minimumPushes(self, word: str) -> int:
5 # Count the occurrences of each character in the string
6 char_count = Counter(word)
7
8 # Initialize the variable to store the minimum pushes
9 minimum_pushes = 0
10
11 # Sort the character counts in descending order
12 sorted_counts = sorted(char_count.values(), reverse=True)
13
14 # Calculate the minimum pushes needed by iterating over the sorted counts
15 for index, count in enumerate(sorted_counts):
16 # Every 8 characters can be grouped and pushed together,
17 # so we divide the index by 8 and add 1 to find the number of pushes.
18 # Multiply by the count of each character to find total pushes.
19 minimum_pushes += ((index // 8) + 1) * count
20
21 # Return the total minimum pushes calculated
22 return minimum_pushes
23
24# Example usage:
25solution = Solution()
26result = solution.minimumPushes("exampleword")
27print(result) # It will print the minimum number of pushes for "exampleword"
28
1import java.util.Arrays; // Required for using Arrays.sort()
2
3class Solution {
4 public int minimumPushes(String word) {
5 // Create an array to hold the frequency of each letter.
6 int[] letterCounts = new int[26];
7
8 // Count the frequency of each letter in the word.
9 for (int i = 0; i < word.length(); ++i) {
10 // Increment the count for the current letter.
11 ++letterCounts[word.charAt(i) - 'a'];
12 }
13
14 // Sort the frequencies in ascending order.
15 Arrays.sort(letterCounts);
16
17 // Initialize the number of pushes to 0.
18 int pushesRequired = 0;
19
20 // Calculate the minimum pushes required.
21 // Iterate over the sorted counts in reverse order (highest to lowest frequency).
22 for (int i = 0; i < 26; ++i) {
23 // The formula determines the number of pushes for each letter
24 // taking into account the increasing number of button push multiplicities
25 // as you move to different sets of 8 buttons.
26 // The index for the counts is adjusted to start from the highest frequency.
27 pushesRequired += (i / 8 + 1) * letterCounts[26 - i - 1];
28 }
29
30 // Return the total number of pushes required to type the word.
31 return pushesRequired;
32 }
33}
34
1class Solution {
2public:
3 // Function to calculate the minimum number of pushes required.
4 int minimumPushes(string word) {
5 // Create a vector to store frequency of each letter in the 'word'.
6 vector<int> frequency(26, 0);
7 // Increment the frequency count for each letter in the 'word'.
8 for (char& c : word) {
9 ++frequency[c - 'a'];
10 }
11
12 // Sort the frequency vector in descending order to prioritize letters with a higher frequency.
13 sort(frequency.rbegin(), frequency.rend());
14
15 // Initialize a variable to store the total number of pushes.
16 int totalPushes = 0;
17 // Loop through the frequency vector to calculate the pushes for each letter.
18 for (int i = 0; i < 26; ++i) {
19 // The number of times a button needs to be pushed is determined by its position in the frequency vector.
20 // (i / 8 + 1) gives the tier of the button (1st-8th, 9th-16th, etc.).
21 // Multiply the tier by the frequency of the letter to get the pushes for that letter.
22 totalPushes += (i / 8 + 1) * frequency[i];
23 }
24
25 // Return the total number of pushes required.
26 return totalPushes;
27 }
28};
29
1function minimumPushes(word: string): number {
2 // Initialize a count array of size 26 to count frequency of each alphabet letter
3 const count: number[] = Array(26).fill(0);
4
5 // Loop through each character in the word to populate the frequency in the count array
6 for (const char of word) {
7 ++count[char.charCodeAt(0) - 'a'.charCodeAt(0)];
8 }
9
10 // Sort the count array in descending order
11 count.sort((a, b) => b - a);
12
13 // Initialize an accumulator for the minimum number of pushes
14 let minimumPushes = 0;
15
16 // Iterate over the count array to calculate the total pushes
17 for (let i = 0; i < 26; ++i) {
18 // The pushes required is determined by the position of the letter
19 // and its frequency in the word. The position is affected by which
20 // group of 8 letters it is in, which is calculated by i/8 and rounded down.
21 // The first group (i/8 = 0) incurs a multiplier of 1, the second a multiplier of 2, etc.
22 minimumPushes += (((i / 8) | 0) + 1) * count[i];
23 }
24
25 // Return the calculated minimum number of pushes
26 return minimumPushes;
27}
28
Time and Space Complexity
The provided code snippet aims to calculate the minimum number of pushes required for a given word. The time and space complexity analysis is as follows:
Time Complexity
The time complexity of the code consists of two main operations:
-
Counter Construction: Constructing the counter object
cnt
from the stringword
. This operation has a time complexity ofO(n)
wheren
is the length of the input stringword
. This is because each character in the string must be visited once to build the frequency count. -
Sorting and Iteration: The second operation is to sort the values obtained from the counter and then to iteratively calculate the total number of pushes. The sorting operation has a time complexity of
O(|\Sigma| * log(|\Sigma|))
, where|\Sigma|
represents the size of the set of unique characters in the string. The iteration over the sorted counts has a complexity ofO(|\Sigma|)
since each element is visited once.
Therefore, the combined time complexity is O(n + |\Sigma| * log(|\Sigma|))
.
Space Complexity
The space complexity of the code is governed by the additional space used by the data structures within the function:
- Counter Object: The counter object
cnt
stores the frequency of each character in the string which requiresO(|\Sigma|)
space, where|\Sigma|
is the number of unique characters inword
.
Summing up the space complexities, the total space complexity is O(|\Sigma|)
.
Learn more about how to find time and space complexity quickly using problem constraints.
What's the output of running the following function using input 56
?
1KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13 def dfs(path, res):
14 if len(path) == len(digits):
15 res.append(''.join(path))
16 return
17
18 next_number = digits[len(path)]
19 for letter in KEYBOARD[next_number]:
20 path.append(letter)
21 dfs(path, res)
22 path.pop()
23
24 res = []
25 dfs([], res)
26 return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2 '2', "abc".toCharArray(),
3 '3', "def".toCharArray(),
4 '4', "ghi".toCharArray(),
5 '5', "jkl".toCharArray(),
6 '6', "mno".toCharArray(),
7 '7', "pqrs".toCharArray(),
8 '8', "tuv".toCharArray(),
9 '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13 List<String> res = new ArrayList<>();
14 dfs(new StringBuilder(), res, digits.toCharArray());
15 return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19 if (path.length() == digits.length) {
20 res.add(path.toString());
21 return;
22 }
23 char next_digit = digits[path.length()];
24 for (char letter : KEYBOARD.get(next_digit)) {
25 path.append(letter);
26 dfs(path, res, digits);
27 path.deleteCharAt(path.length() - 1);
28 }
29}
30
1const KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13 let res = [];
14 dfs(digits, [], res);
15 return res;
16}
17
18function dfs(digits, path, res) {
19 if (path.length === digits.length) {
20 res.push(path.join(''));
21 return;
22 }
23 let next_number = digits.charAt(path.length);
24 for (let letter of KEYBOARD[next_number]) {
25 path.push(letter);
26 dfs(digits, path, res);
27 path.pop();
28 }
29}
30
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
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!