2266. Count Number of Texts


Problem Description

Alice sends text messages to Bob by pressing keys on a phone keypad where multiple letters are mapped to a single digit. To type a specific letter, Alice must press the corresponding digit key a number of times equal to the position of the letter on that key. However, due to a transmission error, Bob receives a string of digits instead of the text message. The task is to determine the total number of possible original text messages that could result in the received string of pressed keys. Note that not all keys are used since the digits '0' and '1' do not map to any letters. The answer could be very large, so it should be returned modulo (10^9 + 7).

For example, the message "bob" would result in Bob receiving "2266622" — '2' pressed three times for 'b', '6' pressed three times for 'o', followed by '2' pressed three times again for the second 'b'.

Intuition

The intuition behind the solution involves recognizing that the task can be tackled as a dynamic programming problem, more specifically, a counting problem. This problem is similar to counting the number of ways to decode a numeric message (like the "Decode Ways" problem on LeetCode), but with the constraint that the sequence of the same digits can represent multiple combinations of letters.

Given the sequence of pressed keys, we can iterate through the sequence and for each contiguous block of identical digits, we calculate the number of ways that block could have been generated by pressing the corresponding letters. The total number of messages is found by multiplying the number of ways for each block, as each block is independent of others.

The sequences of '7' and '9' can have up to four repeated digits (because they correspond to four different letters), while the others can have up to three. We can calculate the number of combinations for each case using separate recurrence relations:

  • For keys with three letters (digits '2'-'6' and '8'), the current number of combinations (f[n]) is the sum of the last three (f[n-1], f[n-2], f[n-3]).
  • For keys with four letters ('7' and '9'), the current number of combinations (g[n]) is the sum of the last four (g[n-1], g[n-2], g[n-3], g[n-4]).

These calculations are done iteratively to fill up the f and g arrays with a precalculated number of combinations for every possible length of digit sequence. Then, in the solution method, we iterate through the pressedKeys by grouping the identical adjacent digits together and calculate the answer by multiplying the possible combinations for each group of digits, ensuring to take the modulo at every step to keep the number within bounds.

Learn more about Math and Dynamic Programming patterns.

Not Sure What to Study? Take the 2-min Quiz to Find Your Missing Piece

What is an advantages of top-down dynamic programming vs bottom-up dynamic programming?

Solution Approach

The solution implements a dynamic programming approach to solve the problem optimally. The main data structure used is a list or an array, which serves as our DP table to store the number of combinations for sequences of each acceptable length. The problem also uses the pattern of grouping which is implemented through the groupby function from Python's itertools module.

Precomputation

In the solution, there are two arrays named f and g initialized with base cases:

  • f starts with [1, 1, 2, 4], representing the number of possible texts for keys with three letters.
  • g starts with [1, 1, 2, 4], representing the number of possible texts for keys '7' and '9' with four letters.

Both arrays are filled up with precomputed values for sequences up to 100,000 characters long. The values are calculated using the recurrent relations mentioned earlier:

  • f[i] = (f[i-1] + f[i-2] + f[i-3]) % mod
  • g[i] = (g[i-1] + g[i-2] + g[i-3] + g[i-4]) % mod

The modulo operation is used to ensure our results stay within the bounds of the large prime number (10^9 + 7), as required by the problem statement.

Dynamic Programming During Execution

The core solution is implemented within the countTexts method of the Solution class:

  1. The method initializes ans to 1, which will hold the final answer.
  2. The input string pressedKeys is iterated by grouping the contiguous identical digits together using groupby.
  3. For each group of identical digits:
    • The length of the group (m) is determined by converting the group iterator to a list and taking its length.
    • The character (ch) representing the group is checked. If it's '7' or '9', we use array g otherwise we use array f.
    • We then determine the number of ways this sequence could be generated using g[m] or f[m].
    • The total ans is updated by multiplying the number of combinations for the current group and taking modulo: ans = (ans * [g[m] if ch in "79" else f[m]]) % mod.
  4. After iterating through all the groups, ans is returned providing the total number of possible text messages.

This approach efficiently breaks down the problem by understanding and identifying the independent parts within the input (pressedKeys) and leveraging precomputed dynamic programming states to calculate the answer.

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

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?

Example Walkthrough

Let's say Alice sends a message that results in Bob receiving the following string of pressed keys: "22233". We will use this to illustrate the solution approach.

  1. To begin, we parse the input and notice that it consists of groups of the digit '2' followed by the digit '3'.
  2. We then initialize our dynamic programming arrays f and g with precomputed values.
    • For simplicity, let's assume we have only computed these arrays for lengths up to 5: f = [1, 1, 2, 4, 7, 13] and g = [1, 1, 2, 4, 8, 15]. These arrays represent the number of ways we can generate a string with three characters (for f) and four characters (for g) mapped to a single key, for strings of different lengths.
  3. We start with an answer (ans) of 1.
  4. We group the pressed keys into contiguous identical digits using groupby. In our case, we have two groups: '222' and '33'.
  5. We iterate through each group and determine the number of combinations for each:
    • For the first group '222' (which has three identical digits):
      • The length of the group (m) is 3.
      • Since the digit 2 corresponds to keys with three letters, we use array f.
      • Looking up f[3], we get 2, meaning there are 2 ways to generate the sequence '222'.
      • So we update our answer: ans = ans * f[3] % mod = 1 * 2 % mod = 2.
    • For the second group '33' (which has two identical digits):
      • The length of the group (m) is 2.
      • Since the digit 3 corresponds to keys with three letters, we use array f.
      • Looking up f[2], we get 1, meaning there is 1 way to generate the sequence '33'.
      • We update our answer again: ans = ans * f[2] % mod = 2 * 1 % mod = 2.
  6. After accounting for all groups, our final answer (ans) is 2, meaning there are 2 possible text messages Alice could have sent to result in the received string "22233".

In this example, the possible original messages could be "abc" (where '2' is pressed once for each letter) followed by "df" (where '3' is pressed once for each letter), or "abc" followed by "ee" (where '3' is pressed twice for 'e'). Therefore, the total number of possible original text messages is indeed 2.

Solution Implementation

1from itertools import groupby
2
3MOD = 10 ** 9 + 7  # Define the modulus for large numbers to ensure the result fits in specified range.
4
5# These lists will hold the number of ways to press keys
6# f for keys that have 3 letters (i.e., 2, 3, 4, 5, 6, 8)
7# g for keys that have 4 letters (i.e., 7 and 9)
8f = [1, 1, 2, 4]  # Base cases for f sequence
9g = [1, 1, 2, 4]  # Base cases for g sequence
10
11# Precompute the answers for up to 100000 presses (which is more than enough for our purpose)
12for _ in range(100000):
13    f.append((f[-1] + f[-2] + f[-3]) % MOD)  # Use modulo operation to keep the number within limits
14    g.append((g[-1] + g[-2] + g[-3] + g[-4]) % MOD)  # Include an additional term for 4-letter keys
15
16class Solution:
17    def countTexts(self, pressedKeys: str) -> int:
18        """Calculates the number of different text messages that can be created based on a string of pressed keys."""
19        answer = 1  # Initialize answer to 1, as we'll multiply the individual counts.
20      
21        # Group by each unique sequence of key presses
22        for key_char, group in groupby(pressedKeys):
23            press_count = len(list(group))  # Count the number of times the key is pressed consecutively
24          
25            # Calculate different ways to press the sequence of keys and update the answer
26            if key_char in "79":
27                answer = (answer * g[press_count]) % MOD  # Use g for keys with 4 letters
28            else:
29                answer = (answer * f[press_count]) % MOD  # Use f for keys with 3 letters
30      
31        # Return the total number of different texts that could be typed
32        return answer
33
1class Solution {
2
3    // Constants
4    private static final int MAX_KEY_PRESS = 100010; // Maximum length of the dynamic programming arrays
5    private static final int MOD = (int) 1e9 + 7; // Modulus value for avoiding integer overflow
6  
7    // Dynamic programming arrays to store intermediate results
8    private static long[] pressSequence3 = new long[MAX_KEY_PRESS]; // Stores results for keys with 3 letters
9    private static long[] pressSequence4 = new long[MAX_KEY_PRESS]; // Stores results for keys with 4 letters
10  
11    // Static block for pre-computing results used in the countTexts function
12    static {
13        pressSequence3[0] = 1;
14        pressSequence3[1] = 1;
15        pressSequence3[2] = 2;
16        pressSequence3[3] = 4;
17
18        pressSequence4[0] = 1;
19        pressSequence4[1] = 1;
20        pressSequence4[2] = 2;
21        pressSequence4[3] = 4;
22
23        // Filling dynamic programming arrays with the possible combinations
24        for (int i = 4; i < MAX_KEY_PRESS; ++i) {
25            // For keys '2', '3', '4', '5', '6', '8'
26            pressSequence3[i] = (pressSequence3[i - 1] + pressSequence3[i - 2] + pressSequence3[i - 3]) % MOD;
27            // For keys '7', '9'
28            pressSequence4[i] = (pressSequence4[i - 1] + pressSequence4[i - 2] + pressSequence4[i - 3] + pressSequence4[i - 4]) % MOD;
29        }
30    }
31
32    public int countTexts(String pressedKeys) {
33        long totalCombinations = 1; // Initialize the total combination count
34        for (int i = 0, n = pressedKeys.length(); i < n; ++i) {
35            int j = i;
36            char currentKey = pressedKeys.charAt(i);
37            // Count the number of times the current key is consecutively pressed
38            while (j + 1 < n && pressedKeys.charAt(j + 1) == currentKey) {
39                ++j;
40            }
41            int pressCount = j - i + 1; // Total presses for the current key
42          
43            // Calculate combinations based on the current key being either a 3-letter or 4-letter key
44            if (currentKey == '7' || currentKey == '9') {
45                // For keys '7' and '9', use pressSequence4 array
46                totalCombinations = totalCombinations * pressSequence4[pressCount];
47            } else {
48                // For other keys, use pressSequence3 array
49                totalCombinations = totalCombinations * pressSequence3[pressCount];
50            }
51            totalCombinations %= MOD; // Modulo to prevent overflow
52            i = j; // Move index to the last occurrence of the currently pressed key
53        }
54        return (int) totalCombinations; // Return the final count as an integer
55    }
56}
57
1#include <vector>
2#include <string>
3
4class Solution {
5    // Constants
6    static constexpr int MAX_KEY_PRESS = 100010; // Maximum length of the dynamic programming arrays
7    static constexpr int MOD = 1000000007;      // Modulus value for avoiding integer overflow
8  
9    // Dynamic programming arrays to store intermediate results
10    static std::vector<long long> pressSequence3; // Stores results for keys with 3 letters
11    static std::vector<long long> pressSequence4; // Stores results for keys with 4 letters
12
13    // Static member initialization
14    static std::vector<long long> initVector(int size) {
15        std::vector<long long> v(size, 0);
16        v[0] = 1;
17        v[1] = 1;
18        v[2] = 2;
19        v[3] = 4;
20        for (int i = 4; i < size; ++i) {
21            v[i] = (v[i - 1] + v[i - 2] + v[i - 3]) % MOD;
22            if (i >= 4) { // For the 4-key sequence
23                v[i] = (v[i] + v[i - 4]) % MOD;
24            }
25        }
26        return v;
27    }
28public:
29    // Constructor
30    Solution() {
31        static std::vector<long long> init_pressSequence3 = initVector(MAX_KEY_PRESS); // Only the first 3 terms differ between pressSequence3 and pressSequence4
32        pressSequence3 = init_pressSequence3;
33        static std::vector<long long> init_pressSequence4 = initVector(MAX_KEY_PRESS); // Hence initializing once using a function
34        pressSequence4 = init_pressSequence4;
35    }
36
37    // Function to count the number of distinct texts that can be generated from the input string of pressed keys
38    int countTexts(std::string pressedKeys) {
39        long long totalCombinations = 1; // Initialize the total combination count
40        for (size_t i = 0, n = pressedKeys.length(); i < n; ++i) {
41            size_t j = i;
42            char currentKey = pressedKeys[i];
43            // Count the number of times the current key is consecutively pressed
44            while (j + 1 < n && pressedKeys[j + 1] == currentKey) {
45                ++j;
46            }
47            int pressCount = j - i + 1; // Total presses for the current key
48          
49            // Calculate combinations based on the current key being either a 3-letter or 4-letter key
50            if (currentKey == '7' || currentKey == '9') {
51                // For keys '7' and '9', use pressSequence4 array
52                totalCombinations = (totalCombinations * pressSequence4[pressCount]) % MOD;
53            } else {
54                // For other keys, use pressSequence3 array
55                totalCombinations = (totalCombinations * pressSequence3[pressCount]) % MOD;
56            }
57            i = j; // Move index to the last occurrence of the currently pressed key
58        }
59        return static_cast<int>(totalCombinations); // Return the final count as an integer
60    }
61};
62
63// Static member definitions
64std::vector<long long> Solution::pressSequence3 = {};
65std::vector<long long> Solution::pressSequence4 = {};
66
1// Constants
2const MAX_KEY_PRESS = 100010; // Maximum length of the dynamic programming arrays
3const MOD = 1e9 + 7; // Modulus value for avoiding integer overflow
4
5// Dynamic programming arrays to store intermediate results
6let pressSequence3: number[] = new Array(MAX_KEY_PRESS); // Stores results for keys with 3 letters
7let pressSequence4: number[] = new Array(MAX_KEY_PRESS); // Stores results for keys with 4 letters
8
9// Pre-computing results used in the countTexts function
10pressSequence3[0] = 1;
11pressSequence3[1] = 1;
12pressSequence3[2] = 2;
13pressSequence3[3] = 4;
14
15pressSequence4[0] = 1;
16pressSequence4[1] = 1;
17pressSequence4[2] = 2;
18pressSequence4[3] = 4;
19
20// Filling dynamic programming arrays with the possible combinations
21for (let i = 4; i < MAX_KEY_PRESS; ++i) {
22    // For keys '2', '3', '4', '5', '6', '8'
23    pressSequence3[i] = (pressSequence3[i - 1] + pressSequence3[i - 2] + pressSequence3[i - 3]) % MOD;
24    // For keys '7', '9'
25    pressSequence4[i] = (pressSequence4[i - 1] + pressSequence4[i - 2] + pressSequence4[i - 3] + pressSequence4[i - 4]) % MOD;
26}
27
28// Function to count the number of distinct text messages that can be created
29function countTexts(pressedKeys: string): number {
30    let totalCombinations: number = 1; // Initialize the total combination count
31
32    for (let i = 0, n = pressedKeys.length; i < n; ++i) {
33        let j = i;
34        const currentKey = pressedKeys.charAt(i);
35        // Count the number of times the current key is consecutively pressed
36        while (j + 1 < n && pressedKeys.charAt(j + 1) === currentKey) {
37            ++j;
38        }
39        const pressCount = j - i + 1; // Total presses for the current key
40
41        // Calculate combinations based on the current key being either a 3-letter or 4-letter key
42        if (currentKey === '7' || currentKey === '9') {
43            // For keys '7' and '9', use pressSequence4 array
44            totalCombinations *= pressSequence4[pressCount];
45        } else {
46            // For other keys, use pressSequence3 array
47            totalCombinations *= pressSequence3[pressCount];
48        }
49        totalCombinations %= MOD; // Apply modulo to prevent overflow
50        i = j; // Move index to the last occurrence of the currently pressed key
51    }
52
53    return totalCombinations; // Return the final count
54}
55
56// You can now call countTexts with a string of pressed keys.
57
Not Sure What to Study? Take the 2-min Quiz

What is the best way of checking if an element exists in an unsorted array once in terms of time complexity? Select the best that applies.

Time and Space Complexity

The given Python code implements a dynamic programming solution to count the possible ways to interpret a string of pressed keys. It initializes precomputed lists f and g for memoization and then implements the function countTexts to compute the result, using these lists.

Time Complexity:

  • The pre-computation of f and g lists is done in a loop that runs for a fixed number of 100,000 iterations. Within each iteration, the code performs a constant number of mathematical operations and list accesses, resulting in a time complexity of O(1) for each iteration. Altogether, the pre-computation of f and g has a time complexity of O(100000) which is considered a constant and hence can be simplified to O(1).
  • The countTexts function iterates over pressedKeys once, and for each unique character, it performs a multiplication and a modulo operation. The complexity of these operations is O(1). If n is the length of the pressedKeys string, the time complexity of this part is O(n).
  • The groupby function has a time complexity of O(n) as well, since it has to iterate through the entire pressedKeys string once.

Combining the pre-computation and the countTexts method, the overall time complexity of the entire code is O(n).

Space Complexity:

  • The f and g lists are of fixed size 100,004 each, and they don't scale with the input, so their space requirement is O(1) in the context of this problem.
  • The countTexts function uses additional space for the output of groupby and the temporary list created for each group s. In the worst-case scenario (e.g., all characters are the same), the space complexity could be O(n). However, since only one group is stored in memory at a time, this does not add up across different groups.
  • Thus, the additional space complexity due to the countTexts function is O(n).

Final Space Complexity is therefore O(n) when considering the storage required for input processing in the countTexts function.

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

Fast Track Your Learning with Our Quick Skills Quiz:

Which of the following is the prefix sum of array [1, 2, 3, 4, 5]?


Recommended Readings


Got a question? Ask the Teaching 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.

←
↑TA đŸ‘šâ€đŸ«