DSAPrep
MediumBacktracking

Letter Combinations of a Phone Number

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 digits to letters (just like on the telephone buttons) is given below. Note that 1 does not map to any letters: 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 = "2"
Output: ["a","b","c"]

Constraints

  • 1 <= digits.length <= 4
  • digits[i] is a digit in the range ['2', '9'].
View original on LeetCode β†—

Each digit maps to a small fixed set of letters, and one letter must be chosen per digit β€” the output length is fixed at len(digits), only the content varies. That makes this a decision tree with a known depth, branching by 3 or 4 at each level.

Brute Force: Iterative Cartesian Product

Time O(4^n Β· n)Space O(4^n Β· n)

Build up the result list by repeatedly taking the Cartesian product with the next digit’s letters: start with [""], and for each digit, form every combination of an existing partial string with every letter of that digit.

class Solution:
def letterCombinations(self, digits: str) -> list[str]:
if not digits:
return []
mapping = {
'2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl',
'6': 'mno', '7': 'pqrs', '8': 'tuv', '9': 'wxyz',
}
combos = ['']
for digit in digits:
combos = [prefix + letter for prefix in combos for letter in mapping[digit]]
return combos

Correct and reasonably efficient, but it materializes a full intermediate list after every digit rather than exploring the tree depth-first β€” for problems with pruning opportunities this pattern would waste work, though here (with no invalid combinations to skip) it costs the same asymptotically as backtracking.

Backtracking, One Digit at a Time

OptimalTime O(4^n Β· n)Space O(n)

Recurse through digits index by index. At each call, try every letter mapped to the current digit: append it to path, recurse into the next digit, then remove it. A combination is complete once path covers every digit.

class Solution:
def letterCombinations(self, digits: str) -> list[str]:
if not digits:
return []
mapping = {
'2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl',
'6': 'mno', '7': 'pqrs', '8': 'tuv', '9': 'wxyz',
}
result = []
path = []
def backtrack(index):
if index == len(digits):
result.append(''.join(path))
return
for letter in mapping[digits[index]]:
path.append(letter)
backtrack(index + 1)
path.pop()
backtrack(0)
return result

Tracing digits = "23" as a decision tree β€” depth 1 branches over 2’s letters (a,b,c), depth 2 over 3’s letters (d,e,f):

""
1 / 5
resultcurrent

Start with an empty path. Digit 0 is "2" -> try a, b, c.

Complexity: letting n = len(digits), each digit contributes at most 4 choices (digits 7 and 9 have 4 letters), so there are up to 4^n complete combinations, each built in O(n) β†’ O(4^n Β· n) time. Recursion depth and path are bounded by n β†’ O(n) auxiliary space (excluding the output).