Facebook Pixel

17. Letter Combinations of a Phone Number

MediumHash TableStringBacktracking
Leetcode Link

Problem Description

You are given a string containing digits from 2 to 9 inclusive. Your task is to return all possible letter combinations that the number could represent, similar to how letters are mapped on a telephone keypad.

The digit-to-letter mapping follows the traditional phone keypad layout:

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

Note that the digit 1 does not map to any letters.

For example:

  • If the input is "23", you need to combine all letters from digit 2 ("abc") with all letters from digit 3 ("def"), resulting in ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
  • If the input is an empty string, return an empty list [].

The answer can be returned in any order.

The solution uses an iterative approach where it processes each digit one by one. Starting with an initial list containing an empty string, it iterates through each digit in the input. For each digit, it looks up the corresponding letters and creates new combinations by appending each letter to all existing combinations. The process continues until all digits have been processed, building up the complete set of letter combinations.

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • No: The problem does not involve nodes and edges or graph traversal. We're dealing with generating combinations from digit-to-letter mappings.

Need to solve for kth smallest/largest?

  • No: We're not looking for the kth smallest or largest element. We need to generate all possible combinations.

Involves Linked Lists?

  • No: The problem doesn't involve linked list data structures or operations.

Does the problem have small constraints?

  • Yes: The input string contains digits from 2-9, and phone numbers typically have a limited length (the problem constraints usually limit the string length to 4 or less). Each digit maps to at most 4 letters, making the total number of combinations manageable.

Brute force / Backtracking?

  • Yes: We need to explore all possible letter combinations systematically. For each digit, we must try every possible letter it maps to and combine it with all possible combinations from the remaining digits. This is a classic backtracking scenario where we build combinations incrementally and explore all paths.

Conclusion: The flowchart suggests using a Backtracking approach for generating all letter combinations. This makes sense because we need to:

  1. Make a choice (pick a letter for the current digit)
  2. Explore further (move to the next digit)
  3. Backtrack if needed (try the next letter for the current digit)
  4. Generate all valid combinations by exploring all possible paths
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

Think about how you would manually write down all possible combinations for a phone number like "23". You'd likely start with the first digit 2 which maps to "abc", and for each letter (a, b, c), you'd combine it with each letter from the second digit 3 which maps to "def" (d, e, f).

This naturally forms a tree-like structure where each level represents a digit, and each branch represents choosing a specific letter for that digit. The root starts empty, and as we go deeper, we build up our combinations letter by letter.

The key insight is that we need to explore every possible path from root to leaf in this decision tree. This is where backtracking shines - it's perfect for systematically exploring all paths in a tree structure.

However, the given solution takes a clever iterative approach instead of traditional recursive backtracking. It builds combinations level by level, similar to breadth-first traversal. Starting with [""] (an empty combination), for each digit encountered:

  1. Take all existing partial combinations
  2. For each partial combination, append each possible letter from the current digit
  3. These new combinations become the foundation for the next digit

For example, with "23":

  • Start: [""]
  • Process 2 ("abc"): ["a", "b", "c"]
  • Process 3 ("def"): ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]

This approach elegantly avoids explicit backtracking by maintaining all valid partial solutions at each step and systematically extending them. It's like building a Cartesian product incrementally - for each new digit, we multiply our existing combinations by the number of letters that digit represents.

The beauty of this solution is its simplicity: the list comprehension [a + b for a in ans for b in s] captures the essence of combining every existing partial solution with every new letter possibility, effectively exploring all branches of our decision tree in just one line.

Learn more about Backtracking patterns.

Solution Approach

The solution uses an iterative traversal approach to build all letter combinations progressively. Let's walk through the implementation step by step:

1. Handle Edge Case:

if not digits:
    return []

If the input string is empty, return an empty list immediately since there are no combinations to generate.

2. Set Up the Digit-to-Letter Mapping:

d = ["abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"]

Create an array where index i corresponds to digit i+2. For example:

  • d[0] = "abc" corresponds to digit 2
  • d[1] = "def" corresponds to digit 3
  • And so on...

3. Initialize the Result:

ans = [""]

Start with a list containing one empty string. This serves as the base for building combinations - we'll append letters to this empty string as we process each digit.

4. Process Each Digit Iteratively:

for i in digits:
    s = d[int(i) - 2]
    ans = [a + b for a in ans for b in s]

For each digit in the input string:

  • Convert the digit character to an integer and subtract 2 to get the correct index in our mapping array
  • Retrieve the corresponding letters (e.g., for digit '2', get "abc")
  • Use a nested list comprehension to create all new combinations:
    • For each existing combination a in ans
    • For each letter b in the current digit's letters s
    • Create a new combination by concatenating a + b

Example Walkthrough for "23":

Initial state: ans = [""]

Processing digit '2' (letters: "abc"):

  • Combine "" with 'a'"a"
  • Combine "" with 'b'"b"
  • Combine "" with 'c'"c"
  • Result: ans = ["a", "b", "c"]

Processing digit '3' (letters: "def"):

  • Combine "a" with each of "def"["ad", "ae", "af"]
  • Combine "b" with each of "def"["bd", "be", "bf"]
  • Combine "c" with each of "def"["cd", "ce", "cf"]
  • Result: ans = ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]

Time Complexity: O(4^n × n) where n is the length of the input string. In the worst case, each digit maps to 4 letters (like digit 7 or 9), giving us 4^n combinations, and each combination has length n.

Space Complexity: O(4^n × n) to store all the generated combinations in the result list.

This iterative approach elegantly avoids recursion and explicit backtracking by maintaining all valid partial solutions at each step and systematically extending them with new letters.

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's walk through the solution with input "23" to see how the iterative approach builds combinations step by step.

Initial Setup:

  • Input: "23"
  • Mapping: d = ["abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"]
  • Start with: ans = [""] (list with one empty string)

Step 1: Process digit '2'

  • Digit '2' maps to index 2-2 = 0 in our array
  • Letters for '2': s = "abc"
  • Apply list comprehension: [a + b for a in ans for b in s]
    • Take "" from ans, append 'a' → "a"
    • Take "" from ans, append 'b' → "b"
    • Take "" from ans, append 'c' → "c"
  • New ans = ["a", "b", "c"]

Step 2: Process digit '3'

  • Digit '3' maps to index 3-2 = 1 in our array
  • Letters for '3': s = "def"
  • Apply list comprehension: [a + b for a in ans for b in s]
    • Take "a" from ans:
      • Append 'd' → "ad"
      • Append 'e' → "ae"
      • Append 'f' → "af"
    • Take "b" from ans:
      • Append 'd' → "bd"
      • Append 'e' → "be"
      • Append 'f' → "bf"
    • Take "c" from ans:
      • Append 'd' → "cd"
      • Append 'e' → "ce"
      • Append 'f' → "cf"
  • Final ans = ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]

Visualization of Growth:

Start:        [""]           (1 combination of length 0)
After '2':    ["a", "b", "c"]  (3 combinations of length 1)
After '3':    ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]  (9 combinations of length 2)

Notice how the number of combinations multiplies: we had 3 partial combinations after processing '2', and since '3' has 3 letters, we end up with 3 × 3 = 9 total combinations. Each iteration extends all existing combinations with every possible letter from the current digit.

Solution Implementation

1class Solution:
2    def letterCombinations(self, digits: str) -> List[str]:
3        # Handle empty input case
4        if not digits:
5            return []
6      
7        # Map each digit (2-9) to corresponding letters on phone keypad
8        # Index 0 corresponds to digit 2, index 1 to digit 3, etc.
9        digit_to_letters = [
10            "abc",   # 2
11            "def",   # 3
12            "ghi",   # 4
13            "jkl",   # 5
14            "mno",   # 6
15            "pqrs",  # 7
16            "tuv",   # 8
17            "wxyz"   # 9
18        ]
19      
20        # Initialize result list with empty string as starting point
21        result = [""]
22      
23        # Process each digit in the input string
24        for digit in digits:
25            # Get the corresponding letters for current digit
26            # Subtract 2 to map digit to correct index (2->0, 3->1, etc.)
27            letters = digit_to_letters[int(digit) - 2]
28          
29            # Generate all combinations by appending each letter 
30            # to all existing combinations
31            result = [existing + letter 
32                     for existing in result 
33                     for letter in letters]
34      
35        return result
36
1class Solution {
2    public List<String> letterCombinations(String digits) {
3        // Initialize result list to store all possible combinations
4        List<String> result = new ArrayList<>();
5      
6        // Handle edge case: empty input
7        if (digits.length() == 0) {
8            return result;
9        }
10      
11        // Start with an empty string to build upon
12        result.add("");
13      
14        // Mapping of digit (2-9) to corresponding letters
15        // Index 0 represents digit '2', index 1 represents digit '3', etc.
16        String[] digitToLetters = new String[] {
17            "abc",  // 2
18            "def",  // 3
19            "ghi",  // 4
20            "jkl",  // 5
21            "mno",  // 6
22            "pqrs", // 7
23            "tuv",  // 8
24            "wxyz"  // 9
25        };
26      
27        // Process each digit in the input string
28        for (char digit : digits.toCharArray()) {
29            // Get the letters corresponding to current digit
30            // Subtract '2' because our array starts at digit 2
31            String letters = digitToLetters[digit - '2'];
32          
33            // Temporary list to store new combinations for current digit
34            List<String> tempCombinations = new ArrayList<>();
35          
36            // For each existing combination in result
37            for (String existingCombination : result) {
38                // Append each letter of current digit to existing combinations
39                for (char letter : letters.toCharArray()) {
40                    tempCombinations.add(existingCombination + letter);
41                }
42            }
43          
44            // Update result with new combinations
45            result = tempCombinations;
46        }
47      
48        return result;
49    }
50}
51
1class Solution {
2public:
3    vector<string> letterCombinations(string digits) {
4        // Return empty vector if input is empty
5        if (digits.empty()) {
6            return {};
7        }
8      
9        // Mapping of digits to corresponding letters (2-9)
10        // Index 0 corresponds to digit '2', index 1 to digit '3', etc.
11        vector<string> digitToLetters = {
12            "abc",  // 2
13            "def",  // 3
14            "ghi",  // 4
15            "jkl",  // 5
16            "mno",  // 6
17            "pqrs", // 7
18            "tuv",  // 8
19            "wxyz"  // 9
20        };
21      
22        // Initialize result with empty string to build upon
23        vector<string> result = {""};
24      
25        // Process each digit in the input string
26        for (char digit : digits) {
27            // Get the corresponding letters for current digit
28            string letters = digitToLetters[digit - '2'];
29          
30            // Temporary vector to store new combinations
31            vector<string> newCombinations;
32          
33            // For each existing combination in result
34            for (const string& existingCombination : result) {
35                // Append each possible letter to create new combinations
36                for (char letter : letters) {
37                    newCombinations.push_back(existingCombination + letter);
38                }
39            }
40          
41            // Replace result with the new combinations
42            result = move(newCombinations);
43        }
44      
45        return result;
46    }
47};
48
1/**
2 * Generates all possible letter combinations that a phone number could represent.
3 * Each digit maps to a set of letters as on a traditional phone keypad:
4 * 2->abc, 3->def, 4->ghi, 5->jkl, 6->mno, 7->pqrs, 8->tuv, 9->wxyz
5 * 
6 * @param digits - A string containing digits from 2-9 inclusive
7 * @returns An array of all possible letter combinations
8 */
9function letterCombinations(digits: string): string[] {
10    // Return empty array if input is empty
11    if (digits.length === 0) {
12        return [];
13    }
14  
15    // Initialize result array with empty string to start building combinations
16    const combinations: string[] = [''];
17  
18    // Phone keypad mappings: index 0 represents digit 2, index 1 represents digit 3, etc.
19    const digitToLetters: string[] = ['abc', 'def', 'ghi', 'jkl', 'mno', 'pqrs', 'tuv', 'wxyz'];
20  
21    // Process each digit in the input string
22    for (const digit of digits) {
23        // Get the corresponding letters for current digit (subtract 2 to align with array index)
24        const letters: string = digitToLetters[Number(digit) - 2];
25      
26        // Temporary array to store new combinations for this iteration
27        const newCombinations: string[] = [];
28      
29        // For each existing combination, append each possible letter
30        for (const existingCombination of combinations) {
31            for (const letter of letters) {
32                // Create new combination by appending current letter to existing combination
33                newCombinations.push(existingCombination + letter);
34            }
35        }
36      
37        // Replace all elements in combinations array with new combinations
38        combinations.splice(0, combinations.length, ...newCombinations);
39    }
40  
41    return combinations;
42}
43

Time and Space Complexity

The time complexity is O(4^n), where n is the length of the input digits string.

Time Complexity Analysis:

  • Each digit maps to at most 4 letters (digits 7 and 9 map to "pqrs" and "wxyz" respectively)
  • For each digit in the input, we iterate through all existing combinations and append each possible letter
  • If we have n digits, and each digit can map to at most 4 letters, the total number of combinations generated is at most 4^n
  • For each combination, we perform string concatenation operations
  • Therefore, the overall time complexity is O(4^n)

The space complexity is O(4^n), where n is the length of the input digits string.

Space Complexity Analysis:

  • The ans list stores all possible letter combinations
  • In the worst case (when all digits map to 4 letters), we generate 4^n combinations
  • Each combination has length n, so technically the total space used is O(n * 4^n)
  • However, since we're counting the number of combinations as the primary factor, the space complexity is considered O(4^n)
  • The intermediate lists created during list comprehension also contribute to space usage, but don't exceed the final result size
  • Therefore, the overall space complexity is O(4^n)

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

Common Pitfalls

1. Incorrect Digit-to-Index Mapping

One of the most common mistakes is incorrectly mapping digits to their corresponding letter groups. Since digits start from 2 (not 0), you need to subtract 2 from the digit to get the correct array index.

Incorrect:

# Wrong: Using digit directly as index
letters = digit_to_letters[int(digit)]  # This would cause IndexError

# Wrong: Subtracting 1 instead of 2
letters = digit_to_letters[int(digit) - 1]  # Maps 2→"def", 3→"ghi" (off by one)

Correct:

letters = digit_to_letters[int(digit) - 2]  # Maps 2→"abc", 3→"def" correctly

2. Forgetting to Handle Empty Input

Without checking for empty input, the function would return [""] instead of [], which is incorrect according to the problem specification.

Incorrect:

def letterCombinations(self, digits: str) -> List[str]:
    digit_to_letters = ["abc", "def", ...]
    result = [""]
    for digit in digits:  # Empty string would skip loop
        # ...
    return result  # Returns [""] for empty input, should be []

Correct:

def letterCombinations(self, digits: str) -> List[str]:
    if not digits:
        return []  # Explicitly handle empty input
    # ... rest of the code

3. Overwriting Result List During Iteration

A subtle bug can occur if you try to modify the result list while iterating over it in the wrong way.

Incorrect:

# This approach modifies result in place incorrectly
result = [""]
for digit in digits:
    letters = digit_to_letters[int(digit) - 2]
    new_result = []
    for existing in result:
        for letter in letters:
            new_result.append(existing + letter)
    result = new_result  # Reassigning is fine, but...
  
# Or worse, trying to modify in place:
for digit in digits:
    letters = digit_to_letters[int(digit) - 2]
    for i in range(len(result)):  # Dangerous if result changes size
        for letter in letters:
            result.append(result[i] + letter)  # This causes infinite loop!

Correct:

# Create a completely new list each iteration
result = [""]
for digit in digits:
    letters = digit_to_letters[int(digit) - 2]
    result = [existing + letter 
             for existing in result 
             for letter in letters]

4. Memory Inefficiency with Large Inputs

While not exactly a bug, the iterative approach stores all intermediate results, which can be memory-intensive. For very long digit strings, consider using a generator or recursive approach with yield to produce results on-demand rather than storing everything at once.

Alternative approach for memory efficiency:

def letterCombinations(self, digits: str):
    if not digits:
        return []
  
    digit_to_letters = ["abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"]
  
    def backtrack(index, path):
        if index == len(digits):
            result.append(path)
            return
      
        letters = digit_to_letters[int(digits[index]) - 2]
        for letter in letters:
            backtrack(index + 1, path + letter)
  
    result = []
    backtrack(0, "")
    return result

This recursive approach uses the call stack instead of maintaining all intermediate combinations, which can be more memory-efficient for certain use cases.

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

Which of the following shows the order of node visit in a Breadth-first Search?


Recommended Readings

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

Load More