Facebook Pixel

2266. Count Number of Texts

Problem Description

Alice is texting Bob using a phone keypad where digits map to letters (like old phone keyboards):

  • 2: abc
  • 3: def
  • 4: ghi
  • 5: jkl
  • 6: mno
  • 7: pqrs
  • 8: tuv
  • 9: wxyz

To type a letter, Alice must press the corresponding digit key multiple times based on the letter's position. For example:

  • To type 's', press '7' four times (since 's' is the 4th letter on key 7)
  • To type 'k', press '5' twice (since 'k' is the 2nd letter on key 5)

When Alice sends a message, Bob doesn't receive the actual text but instead receives the sequence of key presses. For instance, if Alice sends "bob", Bob receives "2266622" (where 2 is pressed twice for 'b', 666 for 'o', and 22 for 'b').

Given a string pressedKeys representing what Bob received, determine how many different text messages Alice could have originally sent. Since the answer can be very large, return it modulo 10^9 + 7.

The challenge is that the same sequence of key presses can represent different messages. For example, "222" could mean:

  • "aaa" (pressing 2 once, three times)
  • "ab" (pressing 2 once for 'a', then twice for 'b')
  • "ba" (pressing 2 twice for 'b', then once for 'a')
  • "c" (pressing 2 three times for 'c')

The solution uses dynamic programming to count possibilities. For consecutive identical digits, it calculates how many ways they can be interpreted:

  • For digits 2, 3, 4, 5, 6, 8 (which map to 3 letters each), a sequence can be broken into groups of 1, 2, or 3 presses
  • For digits 7 and 9 (which map to 4 letters each), a sequence can be broken into groups of 1, 2, 3, or 4 presses

The arrays f and g precompute the number of ways to interpret sequences of length i for 3-letter keys and 4-letter keys respectively, using the recurrence relations:

  • f[i] = f[i-1] + f[i-2] + f[i-3] for 3-letter keys
  • g[i] = g[i-1] + g[i-2] + g[i-3] + g[i-4] for 4-letter keys

The solution groups consecutive identical digits and multiplies the number of ways for each group to get the final answer.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is recognizing that consecutive identical digits create ambiguity in interpretation. When we see "222", we need to figure out all valid ways to split it into letters.

Let's think about this step by step. When we have a sequence of the same digit, we can choose to interpret it in different ways based on how many presses form each letter. For digit '2' (which maps to 'a', 'b', 'c'):

  • 1 press → 'a'
  • 2 presses → 'b'
  • 3 presses → 'c'

So "222" can be split as:

  • [2][2][2] → "aaa"
  • [2][22] → "ab"
  • [22][2] → "ba"
  • [222] → "c"

This is essentially a partitioning problem - we need to count how many ways we can partition a sequence of length n into groups of size 1, 2, or 3 (or 1, 2, 3, 4 for digits 7 and 9).

This naturally leads to a dynamic programming approach. If we know the number of ways to interpret sequences of length i-1, i-2, and i-3, we can compute the ways for length i by considering:

  • Taking the last digit as a single press (use solution for i-1)
  • Taking the last 2 digits as two presses (use solution for i-2)
  • Taking the last 3 digits as three presses (use solution for i-3)

This gives us the recurrence: f[i] = f[i-1] + f[i-2] + f[i-3]

For digits 7 and 9 (which have 4 letters), we extend this to include groups of 4: g[i] = g[i-1] + g[i-2] + g[i-3] + g[i-4]

Since different digit groups are independent (a sequence of 2's doesn't affect how we interpret a sequence of 3's), we can process each group separately and multiply the results together. This multiplication principle works because each choice in one group can be combined with any choice in another group.

The precomputation of arrays f and g is an optimization - instead of computing these values on the fly for each test case, we calculate them once up to a reasonable maximum length (100000 in this case).

Learn more about Math and Dynamic Programming patterns.

Solution Approach

The solution implements the dynamic programming approach with precomputation and grouping.

Step 1: Precomputation of DP Arrays

We precompute two arrays f and g to store the number of ways to interpret sequences of different lengths:

  • f[i]: Number of ways for a sequence of length i where each digit maps to 3 letters (digits 2, 3, 4, 5, 6, 8)
  • g[i]: Number of ways for a sequence of length i where each digit maps to 4 letters (digits 7, 9)

Base cases:

  • f[0] = f[1] = 1: Empty string or single press has 1 way
  • f[2] = 2: Two presses can be "aa" or "b"
  • f[3] = 4: Three presses can be "aaa", "ab", "ba", or "c"
  • Similarly for g with the same initial values

Recurrence relations:

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

We precompute these arrays up to length 100000 to handle any possible input efficiently.

Step 2: Grouping Consecutive Identical Digits

Using Python's groupby function, we group consecutive identical characters in pressedKeys. For example, "22233" becomes groups: ['2', '2', '2'] and ['3', '3'].

Step 3: Calculate Ways for Each Group

For each group:

  1. Determine the character c and count m (length of the group)
  2. If c is '7' or '9', use g[m] (4-letter mapping)
  3. Otherwise, use f[m] (3-letter mapping)

Step 4: Multiply Results

Since groups are independent, we multiply the number of ways for all groups:

ans = ans * (g[m] if c in "79" else f[m]) % mod

The modulo operation is applied at each multiplication step to prevent overflow.

Example Walkthrough:

For input "222":

  • Group: ['2', '2', '2'] with length 3
  • Since '2' maps to 3 letters, use f[3] = 4
  • Result: 4 ways ("aaa", "ab", "ba", "c")

For input "2279":

  • Groups: ['2', '2'] (length 2) and ['7', '9'] (length 1 each)
  • '2' group: f[2] = 2 ways
  • '7' group: g[1] = 1 way
  • '9' group: g[1] = 1 way
  • Total: 2 * 1 * 1 = 2 ways

The algorithm runs in O(n) time where n is the length of pressedKeys, with O(1) space for the actual computation (excluding the precomputed arrays).

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the example "22233" step by step to understand how the solution works.

Input: pressedKeys = "22233"

Step 1: Understanding the Mapping

  • Digit '2' maps to 'abc' (3 letters)
  • Digit '3' maps to 'def' (3 letters)

Step 2: Group Consecutive Digits Using groupby, we get:

  • Group 1: "222" (three 2's)
  • Group 2: "33" (two 3's)

Step 3: Calculate Ways for Each Group

For Group 1 ("222"):

  • Length = 3
  • Since '2' maps to 3 letters, we use f[3]
  • From our precomputed array: f[3] = 4
  • The 4 ways to interpret "222" are:
    • [2][2][2] → 'a' + 'a' + 'a' = "aaa"
    • [2][22] → 'a' + 'b' = "ab"
    • [22][2] → 'b' + 'a' = "ba"
    • [222] → 'c' = "c"

For Group 2 ("33"):

  • Length = 2
  • Since '3' maps to 3 letters, we use f[2]
  • From our precomputed array: f[2] = 2
  • The 2 ways to interpret "33" are:
    • [3][3] → 'd' + 'd' = "dd"
    • [33] → 'e' = "e"

Step 4: Multiply Results

  • Total ways = 4 × 2 = 8

The 8 possible messages are:

  1. "aadd" (from [2][2][2] + [3][3])
  2. "aade" (from [2][2][2] + [33])
  3. "abdd" (from [2][22] + [3][3])
  4. "abe" (from [2][22] + [33])
  5. "badd" (from [22][2] + [3][3])
  6. "bae" (from [22][2] + [33])
  7. "cdd" (from [222] + [3][3])
  8. "ce" (from [222] + [33])

Final Answer: 8

This demonstrates how the solution:

  1. Groups consecutive identical digits
  2. Uses precomputed DP values to find the number of interpretations for each group
  3. Multiplies the results since groups are independent

Solution Implementation

1from itertools import groupby
2from typing import List
3
4# Modulo value for preventing integer overflow
5MOD = 10**9 + 7
6
7# Pre-compute DP values for keys with 3 letters (2,3,4,5,6,8)
8# dp_three[i] represents number of ways to form text with i consecutive presses
9dp_three = [1, 1, 2, 4]
10
11# Pre-compute DP values for keys with 4 letters (7,9)
12# dp_four[i] represents number of ways to form text with i consecutive presses
13dp_four = [1, 1, 2, 4]
14
15# Pre-compute values up to 100000 for both cases
16for _ in range(100000):
17    # For 3-letter keys: can press 1, 2, or 3 times for one letter
18    dp_three.append((dp_three[-1] + dp_three[-2] + dp_three[-3]) % MOD)
19  
20    # For 4-letter keys: can press 1, 2, 3, or 4 times for one letter
21    dp_four.append((dp_four[-1] + dp_four[-2] + dp_four[-3] + dp_four[-4]) % MOD)
22
23
24class Solution:
25    def countTexts(self, pressedKeys: str) -> int:
26        """
27        Count the number of possible text messages that could have been sent.
28      
29        Args:
30            pressedKeys: String of digits representing pressed keys
31          
32        Returns:
33            Number of possible text messages modulo 10^9 + 7
34        """
35        result = 1
36      
37        # Group consecutive identical digits
38        for digit, group in groupby(pressedKeys):
39            # Count the length of consecutive same digits
40            count = len(list(group))
41          
42            # Keys 7 and 9 have 4 letters, others have 3 letters
43            if digit in "79":
44                result = (result * dp_four[count]) % MOD
45            else:
46                result = (result * dp_three[count]) % MOD
47              
48        return result
49
1class Solution {
2    // Constants for array size and modulo value
3    private static final int MAX_SIZE = 100010;
4    private static final int MOD = (int) 1e9 + 7;
5  
6    // dp[i] represents number of possible text messages for i consecutive same digits (2-6, 8)
7    private static long[] dp = new long[MAX_SIZE];
8    // dpSpecial[i] represents number of possible text messages for i consecutive same digits (7, 9)
9    private static long[] dpSpecial = new long[MAX_SIZE];
10  
11    // Static block to precompute all possible combinations
12    static {
13        // Base cases for regular digits (2-6, 8) - max 3 presses per character
14        dp[0] = 1;  // Empty string has 1 way
15        dp[1] = 1;  // Single digit has 1 way
16        dp[2] = 2;  // Two digits can form 2 combinations
17        dp[3] = 4;  // Three digits can form 4 combinations
18      
19        // Base cases for special digits (7, 9) - max 4 presses per character
20        dpSpecial[0] = 1;
21        dpSpecial[1] = 1;
22        dpSpecial[2] = 2;
23        dpSpecial[3] = 4;
24      
25        // Fill the dp arrays using recurrence relation
26        for (int i = 4; i < MAX_SIZE; ++i) {
27            // For regular digits: can split last 1, 2, or 3 digits
28            dp[i] = (dp[i - 1] + dp[i - 2] + dp[i - 3]) % MOD;
29          
30            // For special digits: can split last 1, 2, 3, or 4 digits
31            dpSpecial[i] = (dpSpecial[i - 1] + dpSpecial[i - 2] + 
32                           dpSpecial[i - 3] + dpSpecial[i - 4]) % MOD;
33        }
34    }
35
36    public int countTexts(String pressedKeys) {
37        long result = 1;
38        int length = pressedKeys.length();
39      
40        // Process each group of consecutive same digits
41        for (int i = 0; i < length; ++i) {
42            char currentDigit = pressedKeys.charAt(i);
43            int j = i;
44          
45            // Find the end of consecutive same digits
46            while (j + 1 < length && pressedKeys.charAt(j + 1) == currentDigit) {
47                ++j;
48            }
49          
50            // Calculate the count of consecutive same digits
51            int consecutiveCount = j - i + 1;
52          
53            // Multiply result by number of combinations for this group
54            if (currentDigit == '7' || currentDigit == '9') {
55                // Special digits (7 and 9) can have up to 4 presses
56                result = (result * dpSpecial[consecutiveCount]) % MOD;
57            } else {
58                // Regular digits can have up to 3 presses
59                result = (result * dp[consecutiveCount]) % MOD;
60            }
61          
62            // Move to the next group
63            i = j;
64        }
65      
66        return (int) result;
67    }
68}
69
1class Solution {
2private:
3    static constexpr int MOD = 1e9 + 7;
4    static constexpr int MAX_SIZE = 1e5 + 10;
5  
6    // dp[i] represents number of ways to form text with i consecutive same digits
7    // For digits 2,3,4,5,6,8: each digit maps to 3 letters
8    static long long dpThreeLetters[MAX_SIZE];
9    // For digits 7,9: each digit maps to 4 letters  
10    static long long dpFourLetters[MAX_SIZE];
11  
12    // Static initialization block to precompute DP values
13    static bool initialized;
14    static bool initialize() {
15        // Base cases
16        dpThreeLetters[0] = dpFourLetters[0] = 1;
17        dpThreeLetters[1] = dpFourLetters[1] = 1;
18        dpThreeLetters[2] = dpFourLetters[2] = 2;
19        dpThreeLetters[3] = dpFourLetters[3] = 4;
20      
21        // Fill DP arrays using recurrence relation
22        // For 3-letter digits: can use 1, 2, or 3 consecutive presses
23        for (int i = 4; i < MAX_SIZE; ++i) {
24            dpThreeLetters[i] = (dpThreeLetters[i - 1] + 
25                                  dpThreeLetters[i - 2] + 
26                                  dpThreeLetters[i - 3]) % MOD;
27        }
28      
29        // For 4-letter digits: can use 1, 2, 3, or 4 consecutive presses
30        for (int i = 4; i < MAX_SIZE; ++i) {
31            dpFourLetters[i] = (dpFourLetters[i - 1] + 
32                                dpFourLetters[i - 2] + 
33                                dpFourLetters[i - 3] + 
34                                dpFourLetters[i - 4]) % MOD;
35        }
36      
37        return true;
38    }
39  
40public:
41    int countTexts(string pressedKeys) {
42        long long result = 1;
43        int stringLength = pressedKeys.length();
44      
45        // Process each group of consecutive same digits
46        for (int i = 0; i < stringLength; ++i) {
47            char currentDigit = pressedKeys[i];
48            int groupEnd = i;
49          
50            // Find the end of current group of same digits
51            while (groupEnd + 1 < stringLength && 
52                   pressedKeys[groupEnd + 1] == currentDigit) {
53                ++groupEnd;
54            }
55          
56            // Calculate group size
57            int groupSize = groupEnd - i + 1;
58          
59            // Multiply result by number of ways for this group
60            // Digits 7 and 9 have 4 letters, others have 3 letters
61            if (currentDigit == '7' || currentDigit == '9') {
62                result = (result * dpFourLetters[groupSize]) % MOD;
63            } else {
64                result = (result * dpThreeLetters[groupSize]) % MOD;
65            }
66          
67            // Move to the next group
68            i = groupEnd;
69        }
70      
71        return result;
72    }
73};
74
75// Static member definitions and initialization
76long long Solution::dpThreeLetters[MAX_SIZE];
77long long Solution::dpFourLetters[MAX_SIZE];
78bool Solution::initialized = Solution::initialize();
79
1const MOD = 1e9 + 7;
2const MAX_SIZE = 1e5 + 10;
3
4// dp[i] represents number of ways to form text with i consecutive same digits
5// For digits 2,3,4,5,6,8: each digit maps to 3 letters
6const dpThreeLetters: number[] = new Array(MAX_SIZE);
7// For digits 7,9: each digit maps to 4 letters
8const dpFourLetters: number[] = new Array(MAX_SIZE);
9
10// Initialize DP arrays with precomputed values
11function initializeDPArrays(): void {
12    // Base cases
13    dpThreeLetters[0] = dpFourLetters[0] = 1;
14    dpThreeLetters[1] = dpFourLetters[1] = 1;
15    dpThreeLetters[2] = dpFourLetters[2] = 2;
16    dpThreeLetters[3] = dpFourLetters[3] = 4;
17  
18    // Fill DP arrays using recurrence relation
19    // For 3-letter digits: can use 1, 2, or 3 consecutive presses
20    for (let i = 4; i < MAX_SIZE; i++) {
21        dpThreeLetters[i] = (dpThreeLetters[i - 1] + 
22                            dpThreeLetters[i - 2] + 
23                            dpThreeLetters[i - 3]) % MOD;
24    }
25  
26    // For 4-letter digits: can use 1, 2, 3, or 4 consecutive presses
27    for (let i = 4; i < MAX_SIZE; i++) {
28        dpFourLetters[i] = (dpFourLetters[i - 1] + 
29                           dpFourLetters[i - 2] + 
30                           dpFourLetters[i - 3] + 
31                           dpFourLetters[i - 4]) % MOD;
32    }
33}
34
35// Initialize arrays on module load
36initializeDPArrays();
37
38function countTexts(pressedKeys: string): number {
39    let result = 1;
40    const stringLength = pressedKeys.length;
41  
42    // Process each group of consecutive same digits
43    for (let i = 0; i < stringLength; i++) {
44        const currentDigit = pressedKeys[i];
45        let groupEnd = i;
46      
47        // Find the end of current group of same digits
48        while (groupEnd + 1 < stringLength && 
49               pressedKeys[groupEnd + 1] === currentDigit) {
50            groupEnd++;
51        }
52      
53        // Calculate group size
54        const groupSize = groupEnd - i + 1;
55      
56        // Multiply result by number of ways for this group
57        // Digits 7 and 9 have 4 letters, others have 3 letters
58        if (currentDigit === '7' || currentDigit === '9') {
59            result = (result * dpFourLetters[groupSize]) % MOD;
60        } else {
61            result = (result * dpThreeLetters[groupSize]) % MOD;
62        }
63      
64        // Move to the next group
65        i = groupEnd;
66    }
67  
68    return result;
69}
70

Time and Space Complexity

The time complexity is O(n), where n is the length of the string pressedKeys. This is because:

  • The groupby function iterates through the string once, taking O(n) time
  • For each group, we convert the iterator to a list to get its length, which takes time proportional to the group size
  • The total time for all groups is still O(n) since the sum of all group sizes equals n
  • Array lookups f[m] and g[m] are O(1) operations
  • The modulo and multiplication operations for each group are O(1)

The space complexity is O(n). While the pre-computed arrays f and g have fixed size of 100004 elements (which could be considered O(1) since it's constant), the dominant factor is:

  • The groupby function and converting its result to a list can potentially use O(n) space in the worst case when processing groups
  • In the worst case, if all characters are the same, list(s) creates a list of size n

Therefore, the overall space complexity is O(n) due to the space needed during the grouping and list conversion operations.

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

Common Pitfalls

1. Incorrect Handling of Keys 0 and 1

Pitfall: The problem states that the keypad maps digits 2-9 to letters, but doesn't explicitly mention what happens with 0 and 1. A common mistake is assuming the input will never contain these digits or not handling them properly.

Issue: If '0' or '1' appears in pressedKeys, the current solution will incorrectly treat them as 3-letter keys (since they're not in "79"), leading to wrong results. In reality, these keys don't map to any letters on traditional phone keypads, making any sequence containing them invalid.

Solution:

def countTexts(self, pressedKeys: str) -> int:
    # Check for invalid keys first
    if any(c in '01' for c in pressedKeys):
        return 0  # No valid text can be formed
  
    result = 1
    for digit, group in groupby(pressedKeys):
        count = len(list(group))
        if digit in "79":
            result = (result * dp_four[count]) % MOD
        else:
            result = (result * dp_three[count]) % MOD
    return result

2. Integer Overflow in Intermediate Calculations

Pitfall: Forgetting to apply modulo operation consistently during multiplication can cause integer overflow, especially with large inputs.

Issue: Writing result = result * dp_four[count] % MOD applies modulo after multiplication, which could overflow before the modulo is applied.

Solution: Use parentheses to ensure modulo is applied correctly:

result = (result * dp_four[count]) % MOD

3. Memory Issues with Precomputation

Pitfall: Precomputing arrays up to a fixed size (100000) wastes memory if most test cases are small, and fails if input exceeds this limit.

Issue: The current solution precomputes values for sequences up to length 100000, which uses significant memory and might still be insufficient for edge cases.

Solution: Compute values on-demand with memoization:

from functools import lru_cache

class Solution:
    @lru_cache(maxsize=None)
    def count_ways(self, length: int, max_press: int) -> int:
        if length == 0:
            return 1
        if length < 0:
            return 0
      
        total = 0
        for press in range(1, min(length, max_press) + 1):
            total = (total + self.count_ways(length - press, max_press)) % MOD
        return total
  
    def countTexts(self, pressedKeys: str) -> int:
        result = 1
        for digit, group in groupby(pressedKeys):
            count = len(list(group))
            max_press = 4 if digit in "79" else 3
            result = (result * self.count_ways(count, max_press)) % MOD
        return result

4. Incorrect Base Cases for DP Arrays

Pitfall: Setting wrong initial values for the DP arrays, particularly for index 0.

Issue: Some might initialize dp_three[0] = 0 thinking an empty sequence has no ways, but it should be 1 (representing the empty string).

Solution: Ensure base cases are:

  • dp[0] = 1 (empty string)
  • dp[1] = 1 (single press = single letter)
  • dp[2] = 2 (two presses = two possibilities)
  • dp[3] = 4 (three presses = four possibilities)

5. Misunderstanding the Recurrence Relation

Pitfall: Using the wrong recurrence relation, such as thinking it's a simple Fibonacci sequence.

Issue: Writing dp[i] = dp[i-1] + dp[i-2] instead of considering all possible last letter formations.

Solution: Remember that for 3-letter keys, the last letter can be formed by 1, 2, or 3 presses, so:

dp_three[i] = dp_three[i-1] + dp_three[i-2] + dp_three[i-3]

And for 4-letter keys (7 and 9):

dp_four[i] = dp_four[i-1] + dp_four[i-2] + dp_four[i-3] + dp_four[i-4]
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More