3014. Minimum Number of Pushes to Type Word I


Problem Description

In this problem, you are given a string word that has distinct lowercase English letters. Imagine you have a telephone keypad where each key can be used to type certain letters. Normally, keys 2 to 9 are associated with letters such as key 2 with 'a', 'b', 'c', key 3 with 'd', 'e', 'f', and so on. To type a letter, you need to press a key certain times. For example, to type 'b', you press key 2 twice.

However, in this scenario, you have the ability to remap these keys, meaning you can assign any collection of letters to any key, given that each letter is mapped to exactly one key. Your objective is to find a mapping that allows you to type the given word with the fewest number of key presses.

You need to return the minimum number of key presses required to type the string word after optimally remapping the keys.

Intuition

To solve this problem, we can use a greedy algorithm. Since all letters in the word are distinct and we can remap keys in any way we want, the most efficient approach is to distribute the letters across the keys as evenly as possible. Why? Because this ensures that we use the minimum number of presses per key.

With 8 keys available (keys 2 to 9), the first key will be pressed once for its first letter, twice for its second letter, and so on. By distributing letters evenly, we're minimizing the maximum number of presses for any key.

If the word is shorter than or equal to 8 letters, it can be mapped in such a way that each letter is assigned to a different key, and each key is pressed only once. If the word is longer, we put 8 letters on the first 8 keys; for the next 8 letters, we would need to press each key twice, and so forth. This way, the minimum number of total presses is achieved.

The provided solution calculates the number of presses by adding 8 presses for each complete set of 8 letters and then adding the remaining letters multiplied by the number of letter sets plus one.

Learn more about Greedy and Math patterns.

Solution Approach

The solution provided does not require any complex data structures or advanced patterns due to the simplicity of its greedy algorithm. It focuses on the fact that all letters in the word are unique and can hence be distributed evenly across the 8 keys available.

The algorithm iterates through the string word and calculates the number of times keys need to be pressed in groups of 8 letters. To explain in greater detail:

  1. The variable ans starts at 0 and is used to accumulate the total number of key presses.

  2. The variable k represents the sequence index within the keys; that is, for the first set of up to 8 letters, k is 1, as each key only needs to be pressed once. For the next set of 8 letters (if there are more than 8 unique letters), k would be 2, because we need to press each key twice to access the second letter in each key, and so on.

  3. The for-loop runs for the count of full groups of 8 letters (n // 8) in the word. For each group, it adds to ans the product of k (the current group index) and 8 (the number of letters in the group). Then it increments k to represent the next sequence for the next group of letters.

  4. After processing all full groups of 8, there might be some leftover letters (less than 8). The algorithm adds to the ans the number of leftovers (n % 8) multiplied by the current k value.

  5. Finally, the calculated ans value, which represents the total number of key presses, is returned.

To illustrate with an example, if word is abcdefghijklmnop, the first 8 letters (abcdefgh) would require one press each (total of 8 presses). The next 8 letters (ijklmnop) are the second set and would require two presses each (total of 2 * 8 = 16 presses). So the solution would be 8 + 16 = 24 presses in total.

This completes the explanation of the solution's implementation. It's a straightforward calculation that relies on the rules of evenly distributing unique letters over a fixed number of keys and optimizing for the fewest total presses.

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

What's the output of running the following function using input [30, 20, 10, 100, 33, 12]?

1def fun(arr: List[int]) -> List[int]:
2    import heapq
3    heapq.heapify(arr)
4    res = []
5    for i in range(3):
6        res.append(heapq.heappop(arr))
7    return res
8
1public static int[] fun(int[] arr) {
2    int[] res = new int[3];
3    PriorityQueue<Integer> heap = new PriorityQueue<>();
4    for (int i = 0; i < arr.length; i++) {
5        heap.add(arr[i]);
6    }
7    for (int i = 0; i < 3; i++) {
8        res[i] = heap.poll();
9    }
10    return res;
11}
12
1class HeapItem {
2    constructor(item, priority = item) {
3        this.item = item;
4        this.priority = priority;
5    }
6}
7
8class MinHeap {
9    constructor() {
10        this.heap = [];
11    }
12
13    push(node) {
14        // insert the new node at the end of the heap array
15        this.heap.push(node);
16        // find the correct position for the new node
17        this.bubble_up();
18    }
19
20    bubble_up() {
21        let index = this.heap.length - 1;
22
23        while (index > 0) {
24            const element = this.heap[index];
25            const parentIndex = Math.floor((index - 1) / 2);
26            const parent = this.heap[parentIndex];
27
28            if (parent.priority <= element.priority) break;
29            // if the parent is bigger than the child then swap the parent and child
30            this.heap[index] = parent;
31            this.heap[parentIndex] = element;
32            index = parentIndex;
33        }
34    }
35
36    pop() {
37        const min = this.heap[0];
38        this.heap[0] = this.heap[this.size() - 1];
39        this.heap.pop();
40        this.bubble_down();
41        return min;
42    }
43
44    bubble_down() {
45        let index = 0;
46        let min = index;
47        const n = this.heap.length;
48
49        while (index < n) {
50            const left = 2 * index + 1;
51            const right = left + 1;
52
53            if (left < n && this.heap[left].priority < this.heap[min].priority) {
54                min = left;
55            }
56            if (right < n && this.heap[right].priority < this.heap[min].priority) {
57                min = right;
58            }
59            if (min === index) break;
60            [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61            index = min;
62        }
63    }
64
65    peek() {
66        return this.heap[0];
67    }
68
69    size() {
70        return this.heap.length;
71    }
72}
73
74function fun(arr) {
75    const heap = new MinHeap();
76    for (const x of arr) {
77        heap.push(new HeapItem(x));
78    }
79    const res = [];
80    for (let i = 0; i < 3; i++) {
81        res.push(heap.pop().item);
82    }
83    return res;
84}
85

Example Walkthrough

Let's go through a sample problem to illustrate the solution approach using the word: program.

In this scenario, the word program has 7 unique letters. According to the rules of the telephone keypad, we want to remap these letters to minimize the number of key presses.

Since we have 8 keys (2 to 9) and only 7 unique letters, we can assign each letter to a different key because the number of letters is less than or equal to the number of keys. Here is how we can remap the letters:

  • Key 2: 'p' (1 press)
  • Key 3: 'r' (1 press)
  • Key 4: 'o' (1 press)
  • Key 5: 'g' (1 press)
  • Key 6: 'r' (1 press)
  • Key 7: 'a' (1 press)
  • Key 8: 'm' (1 press)

Since each letter is assigned to its own key, we only need to press each key once to type the word program. This means we only need a total of 7 presses - one press for each letter.

To summarize the steps in the algorithm:

  1. We initialize ans to 0.
  2. We note that k = 1 because we have not yet exceeded 8 unique letters.
  3. There are no full groups of 8, so the for-loop does not add anything to ans.
  4. We then calculate the number of leftover letters, which is 7 % 8 = 7, multiply by k (which is 1), and add the result to ans.
  5. Finally, we have ans = 0 + 7*1 = 7. Therefore, the minimum number of key presses required is 7.

This simple example shows how the provided solution uses the rules of a remappable keypad and a greedy distribution of letters to keys to minimize the number of key presses required to type the word.

Solution Implementation

1class Solution:
2    def minimumPushes(self, word: str) -> int:
3        # Calculate the length of the word
4        word_length = len(word)
5        # Initialize 'total_pushes' to store the total pushes required
6        # Initialize 'pushes_per_batch' which represents the number of pushes for each batch of 8 characters
7        total_pushes, pushes_per_batch = 0, 1
8      
9        # Process all complete batches of 8 characters
10        for _ in range(word_length // 8):
11            total_pushes += pushes_per_batch * 8  # Add the pushes required for a batch of 8 characters
12            pushes_per_batch += 1  # Increment the number of pushes needed for each subsequent batch
13      
14        # Add the pushes required for the remaining characters, if any
15        total_pushes += pushes_per_batch * (word_length % 8)
16      
17        # Return the total number of pushes required
18        return total_pushes
19
1class Solution {
2    public int minimumPushes(String word) {
3        int wordLength = word.length(); // Length of the string word
4        int totalPushes = 0; // Total number of pushes required
5        int pushMultiplier = 1; // Multiplier for each group of 8 characters
6
7        // Loop through the groups of 8 characters in the word
8        for (int i = 0; i < wordLength / 8; ++i) {
9            totalPushes += pushMultiplier * 8; // Add pushes for this group
10            ++pushMultiplier; // Increment the multiplier for the next group
11        }
12
13        // Add pushes for the remaining characters (if any)
14        totalPushes += pushMultiplier * (wordLength % 8);
15
16        return totalPushes; // Return the total number of pushes
17    }
18}
19
1class Solution {
2public:
3    // Function to calculate the minimum number of pushes needed
4    int minimumPushes(string word) {
5        int length = word.size();  // Get the length of the input string
6        int answer = 0;            // Initialize the answer to zero
7        int multiplier = 1;        // Initialize the multiplier to one
8
9        // Process complete sets of 8 characters
10        for (int i = 0; i < length / 8; ++i) {
11            answer += multiplier * 8;  // Add 8 times the current multiplier to the answer
12            ++multiplier;              // Increment the multiplier for the next set
13        }
14
15        // Add the remaining characters (less than 8 if any)
16        answer += multiplier * (length % 8);
17
18        // Return the total minimum pushes calculated
19        return answer;
20    }
21};
22
1/**
2 * Calculate the minimum number of pushes required based on the "word" string's length.
3 * Each set of 8 characters requires an increasing number of pushes.
4 *
5 * @param {string} word - The string to be analyzed for push calculations.
6 * @return {number} - The minimum number of pushes required.
7 */
8function minimumPushes(word: string): number {
9    // Get the length of the input string.
10    const wordLength = word.length;
11    // Initialize the answer to be returned with 0.
12    let totalPushes = 0;
13    // Start with k = 1, which will be multiplied with the character count.
14    let multiplier = 1;
15  
16    // Loop through the string 8 characters at a time.
17    for (let i = 0; i < (wordLength / 8) | 0; ++i) {
18        totalPushes += multiplier * 8; // Add 8 times the current multiplier to the total.
19        ++multiplier; // Increment the multiplier for the next set of characters.
20    }
21  
22    // Add the remaining characters multiplied by the current multiplier
23    // for the case where word length is not a multiple of 8.
24    totalPushes += multiplier * (wordLength % 8);
25  
26    // Return the total number of pushes calculated.
27    return totalPushes;
28}
29

Time and Space Complexity

The time complexity of the given code is O(1), also described as constant time complexity. The reason for this is that the loop runs a fixed number of times that depends on the length of the string word divided by 8. Since division is a constant-time operation, and the loop consequently runs a constant number of iterations (specifically, n // 8 times), the complexity does not grow with the size of the input but remains capped. The modifications inside the loop are also constant-time operations, resulting in an overall constant time complexity.

The space complexity of the code is O(1), meaning it requires a constant amount of additional space that does not grow with the size of the input. This is because the variables n, ans, k, and _ each occupy a fixed amount of space, and no additional space that scales with the input size (word) is required or allocated during the execution of the code.

Learn more about how to find time and space complexity quickly using problem constraints.


Fast Track Your Learning with Our Quick Skills Quiz:

What's the output of running the following function using the following tree as input?

1def serialize(root):
2    res = []
3    def dfs(root):
4        if not root:
5            res.append('x')
6            return
7        res.append(root.val)
8        dfs(root.left)
9        dfs(root.right)
10    dfs(root)
11    return ' '.join(res)
12
1import java.util.StringJoiner;
2
3public static String serialize(Node root) {
4    StringJoiner res = new StringJoiner(" ");
5    serializeDFS(root, res);
6    return res.toString();
7}
8
9private static void serializeDFS(Node root, StringJoiner result) {
10    if (root == null) {
11        result.add("x");
12        return;
13    }
14    result.add(Integer.toString(root.val));
15    serializeDFS(root.left, result);
16    serializeDFS(root.right, result);
17}
18
1function serialize(root) {
2    let res = [];
3    serialize_dfs(root, res);
4    return res.join(" ");
5}
6
7function serialize_dfs(root, res) {
8    if (!root) {
9        res.push("x");
10        return;
11    }
12    res.push(root.val);
13    serialize_dfs(root.left, res);
14    serialize_dfs(root.right, res);
15}
16

Recommended Readings


Got a question? Ask the Monster Assistant anything you don't understand.

Still not clear? Ask in the Forum,  Discord or Submit the part you don't understand to our editors.


🪄