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 keysg[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.
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 lengthi
where each digit maps to 3 letters (digits 2, 3, 4, 5, 6, 8)g[i]
: Number of ways for a sequence of lengthi
where each digit maps to 4 letters (digits 7, 9)
Base cases:
f[0] = f[1] = 1
: Empty string or single press has 1 wayf[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:
- Determine the character
c
and countm
(length of the group) - If
c
is '7' or '9', useg[m]
(4-letter mapping) - 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 EvaluatorExample 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:
- "aadd" (from [2][2][2] + [3][3])
- "aade" (from [2][2][2] + [33])
- "abdd" (from [2][22] + [3][3])
- "abe" (from [2][22] + [33])
- "badd" (from [22][2] + [3][3])
- "bae" (from [22][2] + [33])
- "cdd" (from [222] + [3][3])
- "ce" (from [222] + [33])
Final Answer: 8
This demonstrates how the solution:
- Groups consecutive identical digits
- Uses precomputed DP values to find the number of interpretations for each group
- 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, takingO(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 equalsn
- Array lookups
f[m]
andg[m]
areO(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 useO(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 sizen
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]
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
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
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!