Letter Combinations of a Phone Number - Complete Guide
Letter Combinations of a Phone Number
Problem Statement
Given a string containing digits from 2-9 inclusive, return all possible letter combinations that the number could represent. Return the answer in any order.
A mapping of digit to letters (just like on the telephone buttons) is given below. Note that 1 does not map to any letters.
| Digit | Letters |
|-------|---------|
| 2 | abc |
| 3 | def |
| 4 | ghi |
| 5 | jkl |
| 6 | mno |
| 7 | pqrs |
| 8 | tuv |
| 9 | wxyz |
Understanding the Problem
This is a classic Combinatorial Generation problem. We are not looking for a specific combination (like a password), but rather all valid combinations. The input is a sequence of digits, and the output is a sequence of strings.
Key Observations
- Input Length: The number of digits determines the depth of recursion. If the input has
ndigits, the recursion depth will ben. - Branching Factor: Each digit maps to 3 or 4 letters. This means at each step of the recursion, we have multiple choices (branches).
- Combination Order: The order of combinations matters in terms of generation (e.g., "ad" comes before "ae"), but the problem allows any order in the final output.
The Backtracking Approach
Backtracking is a general algorithm for finding all (or some) solutions to some computational problems, notably constraint satisfaction problems, that incrementally builds candidates to the solutions, and abandons a candidate ("backtracks") as soon as it determines that the candidate cannot possibly be completed to a valid solution.
Why Backtracking?
We can solve this using simple loops, but Backtracking is the most natural fit because:
- We are building a string character by character.
- We need to explore all possible paths (all combinations).
- Once we reach the end of the digits, we have found a valid combination.
The Algorithm
- Mapping: Create a dictionary/hash map to store the digit-to-letter mappings.
- Base Case: If the current index is equal to the length of the input string, we have successfully built a combination. Add it to our result list.
- Recursive Step: For the current digit, iterate through all its corresponding letters. For each letter, append it to the current combination and recursively call the function for the next index.
- Backtracking: The recursion automatically handles "backtracking" because when a recursive call returns, the function continues to the next letter in the loop, effectively removing the last added letter and trying a different one.
Step-by-Step Walkthrough (Input: "23")
- Start:
index = 0,current = "" - Digit '2' (abc):
- Pick 'a': Recurse with
index=1,current="a" - Pick 'b': Recurse with
index=1,current="b" - Pick 'c': Recurse with
index=1,current="c"
- Digit '3' (def):
- From 'a': Pick 'd' -> "ad", Pick 'e' -> "ae", Pick 'f' -> "af". (Base case reached)
- From 'b': Pick 'd' -> "bd", Pick 'e' -> "be", Pick 'f' -> "bf". (Base case reached)
- From 'c': Pick 'd' -> "cd", Pick 'e' -> "ce", Pick 'f' -> "cf". (Base case reached)
Implementation
TypeScript/JavaScript
function letterCombinations(digits: string): string[] {
// Edge case: empty input
if (!digits) return [];
// Mapping of digits to letters
const digitMap: { [key: string]: string } = {
'2': 'abc',
'3': 'def',
'4': 'ghi',
'5': 'jkl',
'6': 'mno',
'7': 'pqrs',
'8': 'tuv',
'9': 'wxyz'
};
const result: string[] = [];
// Recursive Backtracking Function
function backtrack(index: number, currentCombination: string) {
// Base Case: If we have processed all digits
if (index === digits.length) {
result.push(currentCombination);
return;
}
// Get the current digit
const currentDigit = digits[index];
// Get the corresponding letters
const letters = digitMap[currentDigit];
// Iterate through each letter for the current digit
for (const letter of letters) {
// Add letter to current combination
backtrack(index + 1, currentCombination + letter);
}
}
// Start the recursion
backtrack(0, "");
return result;
}Python
def letterCombinations(digits: str) -> list[str]:
if not digits:
return []
digit_map = {
'2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl',
'6': 'mno', '7': 'pqrs', '8': 'tuv', '9': 'wxyz'
}
result = []
def backtrack(index: int, current: str):
if index == len(digits):
result.append(current)
return
for letter in digit_map[digits[index]]:
backtrack(index + 1, current + letter)
backtrack(0, "")
return resultComplexity Analysis
Time Complexity: O(3^N * 4^M)
- N: The number of digits in the input string.
- M: The number of digits that map to 4 letters (7 and 9).
For every digit, we have a branching factor. Most digits have 3 letters, but digits 7 and 9 have 4 letters.
- If all digits had 3 letters, the total combinations would be
3^N. - Since 7 and 9 have 4 letters, we multiply by
4^M. - Additionally, we spend
O(N)time to build the string at each level (concatenation), but this is often dominated by the exponential growth of the combinations.
Space Complexity: O(N)
- Recursion Stack: The maximum depth of the recursion is equal to the number of digits
N. - Output Storage: We store all combinations in the result array. The total number of characters stored is
O(3^N 4^M N), but this is the output size, not auxiliary space. The auxiliary space (excluding output) isO(N).
Optimization Tips
- String Concatenation: In languages like Python or JavaScript, string concatenation inside a loop (
current + letter) can be inefficient because strings are immutable. It creates a new string object every time. While acceptable for small inputs, for very large inputs, it is better to use a list/array to build the string and join it at the end.
Optimized Python approach: Use a list path to store characters, append at the end, and pop (backtrack) when returning.
- Iterative Approach (Queue): You can solve this iteratively using a queue. Start with an empty string. For each digit, take all current strings in the queue, append each letter of the digit to them, and add the new strings back to the queue. This effectively simulates the backtracking tree without the call stack overhead.
Common Mistakes
- Ignoring the Empty Input: If the input string is empty, the function should return an empty array, not
[""]ornull. - Incorrect Base Case: The base case must be when
indexequals the length of the digits string, not when the current combination is empty. - Modifying the Input Map: Ensure you are not modifying the original mapping dictionary during the recursion.
Real-World Applications
- T9 Texting: The algorithm is directly used in older mobile phones to predict words based on pressed keys.
- Dialing Codes: Generating all possible combinations for area codes or extension numbers.
- Password Generation: Creating all possible variations of a numeric PIN based on a set of rules.
Summary
The "Letter Combinations of a Phone Number" problem is a quintessential example of Backtracking. It teaches us how to systematically explore a state space tree, where each node represents a partial solution. By defining a clear base case (reaching the end of the input) and a recursive step (adding a letter and moving to the next digit), we can efficiently generate all valid combinations.