Skip to content

264. Letter Combinations of a Phone Number

🔗 LeetCode Problem: 17. Letter Combinations of a Phone Number
📊 Difficulty: Medium
🏷️ Topics: Backtracking, Recursion

Problem Statement

Given a string containing digits from 2-9 inclusive, return all possible letter combinations that the number could represent.

A mapping of digits to letters (just like on telephone buttons):

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

Example 1:

Input: digits = "23"
Output: ["ad","ae","af","bd","be","bf","cd","ce","cf"]

Example 2:

Input: digits = ""
Output: []

Example 3:

Input: digits = "2"
Output: ["a","b","c"]

Constraints: - 0 <= digits.length <= 4 - digits[i] is a digit in the range ['2', '9'].


Understanding the Problem

Let me work through digits = "23" by hand.

Digit '2' maps to: "abc" Digit '3' maps to: "def"

I need all combinations:

Pick from digit 2    Pick from digit 3    Result
      a          +         d          =    "ad"
      a          +         e          =    "ae"
      a          +         f          =    "af"
      b          +         d          =    "bd"
      b          +         e          =    "be"
      b          +         f          =    "bf"
      c          +         d          =    "cd"
      c          +         e          =    "ce"
      c          +         f          =    "cf"

Total: 9 combinations (3 × 3)

Pattern: For each digit, try every letter it maps to.


Visualizing as Tree

For digits = "23":

                    ""
                    |
        -------------------------
        |           |           |
       "a"         "b"         "c"    ← digit 2
        |           |           |
    ---------   ---------   ---------
    |   |   |   |   |   |   |   |   |
   "ad""ae""af" "bd""be""bf" "cd""ce""cf"  ← digit 3

Leaf nodes = answer

At each level (digit), I make a choice (pick a letter).


Approach 1: Recursion (Backtracking)

Recursive Logic

At index i:
  - If i == length, we've built complete combination → add to result
  - Otherwise:
      Get letters for digits[i]
      For each letter:
        - Add letter to current
        - Recurse for index i+1
        - Remove letter (backtrack)

Code

import java.util.*;

class Solution {
    private List<String> result;
    private String[] mapping;

    public List<String> letterCombinations(String digits) {
        result = new ArrayList<>();

        if (digits == null || digits.length() == 0) {
            return result;
        }

        mapping = new String[] {
            "",     // 0
            "",     // 1
            "abc",  // 2
            "def",  // 3
            "ghi",  // 4
            "jkl",  // 5
            "mno",  // 6
            "pqrs", // 7
            "tuv",  // 8
            "wxyz"  // 9
        };

        backtrack(0, new StringBuilder(), digits);
        return result;
    }

    private void backtrack(int index, StringBuilder current, String digits) {
        // Base case
        if (index == digits.length()) {
            result.add(current.toString());
            return;
        }

        // Get letters for current digit
        String letters = mapping[digits.charAt(index) - '0'];

        // Try each letter
        for (int i = 0; i < letters.length(); i++) {
            char letter = letters.charAt(i);

            // Choose
            current.append(letter);

            // Explore
            backtrack(index + 1, current, digits);

            // Unchoose (backtrack)
            current.deleteCharAt(current.length() - 1);
        }
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.letterCombinations("23"));
        System.out.println(sol.letterCombinations(""));
        System.out.println(sol.letterCombinations("2"));
    }
}

Dry Run

digits = "23"

backtrack(0, "", "23"):
  index = 0, digit = '2', letters = "abc"

  i = 0, letter = 'a':
    current = "a"
    backtrack(1, "a", "23"):
      index = 1, digit = '3', letters = "def"

      i = 0, letter = 'd':
        current = "ad"
        backtrack(2, "ad", "23"):
          index = 2 == length → add "ad" ✓
        current = "a" (deleted 'd')

      i = 1, letter = 'e':
        current = "ae"
        backtrack(2, "ae", "23"):
          index = 2 == length → add "ae" ✓
        current = "a" (deleted 'e')

      i = 2, letter = 'f':
        current = "af"
        backtrack(2, "af", "23"):
          index = 2 == length → add "af" ✓
        current = "a" (deleted 'f')

    current = "" (deleted 'a')

  i = 1, letter = 'b':
    current = "b"
    backtrack(1, "b", "23"):
      index = 1, digit = '3', letters = "def"

      i = 0, letter = 'd':
        current = "bd"
        backtrack(2, "bd", "23"):
          index = 2 == length → add "bd" ✓
        current = "b"

      i = 1, letter = 'e':
        current = "be"
        backtrack(2, "be", "23"):
          index = 2 == length → add "be" ✓
        current = "b"

      i = 2, letter = 'f':
        current = "bf"
        backtrack(2, "bf", "23"):
          index = 2 == length → add "bf" ✓
        current = "b"

    current = "" (deleted 'b')

  i = 2, letter = 'c':
    current = "c"
    backtrack(1, "c", "23"):
      index = 1, digit = '3', letters = "def"

      i = 0, letter = 'd':
        current = "cd"
        backtrack(2, "cd", "23"):
          index = 2 == length → add "cd" ✓
        current = "c"

      i = 1, letter = 'e':
        current = "ce"
        backtrack(2, "ce", "23"):
          index = 2 == length → add "ce" ✓
        current = "c"

      i = 2, letter = 'f':
        current = "cf"
        backtrack(2, "cf", "23"):
          index = 2 == length → add "cf" ✓
        current = "c"

    current = "" (deleted 'c')

Result: ["ad","ae","af","bd","be","bf","cd","ce","cf"] ✓

Understanding Backtracking

The pattern:

current.append(letter);                    // Choose
backtrack(index + 1, current, digits);     // Explore
current.deleteCharAt(current.length() - 1); // Unchoose

Why unchoose?

After exploring with 'a', we need to try 'b'. But current still has 'a'! So we remove it to restore original state.

current = ""
Try 'a': current = "a" → explore all paths → current = "" (restore)
Try 'b': current = "b" → explore all paths → current = "" (restore)
Try 'c': current = "c" → explore all paths → current = "" (restore)

This is backtracking - undo choice to try next option.

Complexity

Time: O(4^n × n) - 4^n combinations (worst case, digits 7 and 9 have 4 letters) - n to build each string

Space: O(n) - Recursion depth = n


Approach 2: Iterative (BFS)

The Idea

Build combinations level by level using a queue.

For digits = "23":

Level 0: [""]

Level 1 (digit '2'):
  Take "", add 'a','b','c' → ["a", "b", "c"]

Level 2 (digit '3'):
  Take "a", add 'd','e','f' → ["ad", "ae", "af"]
  Take "b", add 'd','e','f' → ["bd", "be", "bf"]
  Take "c", add 'd','e','f' → ["cd", "ce", "cf"]
  Queue: ["ad","ae","af","bd","be","bf","cd","ce","cf"]

Code

import java.util.*;

class Solution {
    public List<String> letterCombinations(String digits) {
        List<String> result = new ArrayList<>();

        if (digits == null || digits.length() == 0) {
            return result;
        }

        String[] mapping = new String[] {
            "",     // 0
            "",     // 1
            "abc",  // 2
            "def",  // 3
            "ghi",  // 4
            "jkl",  // 5
            "mno",  // 6
            "pqrs", // 7
            "tuv",  // 8
            "wxyz"  // 9
        };

        Queue<String> queue = new LinkedList<>();
        queue.offer("");

        for (int i = 0; i < digits.length(); i++) {
            String letters = mapping[digits.charAt(i) - '0'];
            int size = queue.size();

            for (int j = 0; j < size; j++) {
                String current = queue.poll();

                for (char letter : letters.toCharArray()) {
                    queue.offer(current + letter);
                }
            }
        }

        result.addAll(queue);
        return result;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.letterCombinations("23"));
    }
}

Dry Run

digits = "23"
queue = [""]

i = 0, digit = '2', letters = "abc", size = 1:
  j = 0:
    current = "" (poll)
    Add 'a': queue = ["a"]
    Add 'b': queue = ["a", "b"]
    Add 'c': queue = ["a", "b", "c"]

i = 1, digit = '3', letters = "def", size = 3:
  j = 0:
    current = "a" (poll)
    Add 'd': queue = ["b", "c", "ad"]
    Add 'e': queue = ["b", "c", "ad", "ae"]
    Add 'f': queue = ["b", "c", "ad", "ae", "af"]

  j = 1:
    current = "b" (poll)
    Add 'd': queue = ["c", "ad", "ae", "af", "bd"]
    Add 'e': queue = ["c", "ad", "ae", "af", "bd", "be"]
    Add 'f': queue = ["c", "ad", "ae", "af", "bd", "be", "bf"]

  j = 2:
    current = "c" (poll)
    Add 'd': queue = ["ad", "ae", "af", "bd", "be", "bf", "cd"]
    Add 'e': queue = ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce"]
    Add 'f': queue = ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]

Result: ["ad","ae","af","bd","be","bf","cd","ce","cf"] ✓

Complexity

Time: O(4^n × n) Space: O(4^n) - Queue holds all combinations


Comparison

┌─────────────┬──────────────┬──────────────┐
│ Aspect      │ Recursion    │ Iteration    │
├─────────────┼──────────────┼──────────────┤
│ Time        │ O(4^n × n)   │ O(4^n × n)   │
│ Space       │ O(n)         │ O(4^n)       │
│ Code        │ Shorter      │ Longer       │
│ Natural     │ Yes ✓        │ No           │
│ Interview   │ Preferred ✓  │ Alternative  │
└─────────────┴──────────────┴──────────────┘

Recommendation: Use recursion (backtracking) as primary approach.


Quick Revision

Quick Implementation

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class Solution {
  public List<String> letterCombinations(String letters) {
    HashMap<Integer, String> map = new HashMap<>();
    map.put(0, "");
    map.put(1, "");
    map.put(2, "abc");
    map.put(3, "def");
    map.put(4, "ghi");
    map.put(5, "jkl");
    map.put(6, "mno");
    map.put(7, "pqrs");
    map.put(8, "tuv");
    map.put(9, "wxyz");

    // return recursive(letters, map);

    return iterative(letters, map);
  }

  private List<String> iterative(String letters, HashMap<Integer, String> map) {
    // step 1: create a queue and add empty for now
    Queue<String> queue = new LinkedList<>();
    queue.offer("");

    // step 2: kind of algo or step to remember only.
    // everytime take next letter from "letters", for example, 2 from 23.
    // everytime take 1 from queue and append with all chars from "2" and add to
    // queue
    for (int i = 0; i < letters.length(); i++) {
      // for i = 0, "abc" for 2
      // for i = 1, "def" for 3
      String mapping = map.get(letters.charAt(i) - '0');
      // for i = 0, len becomes 0 and polled is ""
      // for i = 1, len becomes 3 and polled are "a", "b" and "c"
      int len = queue.size();

      while (len > 0) {
        String polled = queue.poll();

        // step 3: combine every letter from mapping with polled item
        for (int j = 0; j < mapping.length(); j++) {
          // for i = 0, "a", "b", "c" gets polled to queue.
          queue.offer(polled.concat(String.valueOf(mapping.charAt(j))));
        }

        len--;
      }
    }

    // step 4: put remaining items on the queue to res list which is the result
    List<String> res = new ArrayList<>();
    while (!queue.isEmpty()) {
      res.add(queue.poll());
    }

    return res;
  }

  private List<String> recursive(String letters, HashMap<Integer, String> map) {
    List<String> res = new ArrayList<>();

    recursiveUtil(letters, map, 0, new StringBuilder(), res);

    return res;
  }

  private void recursiveUtil(String letters, HashMap<Integer, String> map, int currIndex, StringBuilder currString,
      List<String> res) {
    // step 5: base case
    if (currIndex == letters.length()) {
      res.add(currString.toString());

      return;
    }

    // step 1: iterate all characters of the digit.
    // for example, in "23", for 2, here you need to loop for "abc"
    // when "a" is done in first iteration, go for next index which is "3" and there
    // we need to loop for "def" and hence it will become "ad", "ae" and "af"
    // in "23" with currIndex as 0
    // letters.charAt(currIndex) => "23".charAt(0) => ascii(2) - '0' => 2 => "abc"
    String mapping = map.get(letters.charAt(currIndex) - '0');

    for (int i = 0; i < mapping.length(); i++) {
      // step 2: add a
      currString.append(mapping.charAt(i));

      // step 3: proceed for next letter from the next digit
      recursiveUtil(letters, map, currIndex + 1, currString, res);

      // step 4: remove a from curr / temp
      currString.deleteCharAt(currString.length() - 1);
    }
  }

  public static void main(String[] args) {
    Solution s = new Solution();
    System.out
        .println(s.letterCombinations("23").equals(List.of("ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf")));
  }
}

Summary

Backtracking Pattern:

void backtrack(state) {
    if (goal reached) {
        add to result
        return
    }

    for each choice {
        make choice
        backtrack(next state)
        undo choice
    }
}

Key Insight: Backtracking explores all possibilities by trying each choice, exploring recursively, then undoing to try next choice.

This is the foundation for all backtracking problems! ✓