Skip to content

17. Letter Combinations of a Phone Number (Medium)

Problem

Given a string of digits 2-9, return all possible letter combinations the digits could represent on a standard phone keypad. 1 has no letters; 0 is not in the input.

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

Example

  • digits = "23"["ad","ae","af","bd","be","bf","cd","ce","cf"]
  • digits = ""[]
  • digits = "2"["a","b","c"]

LeetCode 17 · Link · Medium

Try it yourself

idle

Starter code: this editor begins with intentional TODOs. Fill the function, run the embedded tests, then compare your solution with the worked approaches below.

Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Approach 1: Iterative cartesian product

Build combinations digit-by-digit, extending each partial string with each letter of the next digit.

def letter_combinations(digits):
if not digits:
return []
mapping = {"2":"abc","3":"def","4":"ghi","5":"jkl",
"6":"mno","7":"pqrs","8":"tuv","9":"wxyz"}
result = [""]
for d in digits: # L1: loop over n digits
result = [prefix + ch for prefix in result for ch in mapping[d]] # L2: extend each combo
return result

Where the time goes, line by line

Variables: n = len(digits), k = average letters per digit (3 or 4).

LinePer-call costTimes executedContribution
L1 (outer loop)O(1)O(1) overheadnO(n)O(n)
L2 (list comprehension)O(kresult)O(k · \|result\|)nO(knn)O(k^n · n) ← dominates

After processing digit i, result has k^i entries each of length i. The comprehension at step i processes k^(i-1) × k entries. Total work is the sum over i of i × k^i = O(nkn)O(n · k^n).

Complexity

  • Time: O(4nn)O(4^n · n) worst case, driven by L2 growing result at every step.
  • Space: O(4nn)O(4^n · n) for the output.

Try this approach:

idle
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).
final class Solution {
func letterCombinations(_ digits: String) -> [String] {
let map: [Character: [Character]] = ["2": Array("abc"), "3": Array("def"), "4": Array("ghi"), "5": Array("jkl"), "6": Array("mno"), "7": Array("pqrs"), "8": Array("tuv"), "9": Array("wxyz")]
var combinations = [""]
for digit in digits { var next: [String] = []; for prefix in combinations { for letter in map[digit]! { next.append(prefix + String(letter)) } }; combinations = next }
return digits.isEmpty ? [] : combinations
}
}

Approach 2: Backtracking DFS (canonical)

Recursively build one combination at a time.

def letter_combinations(digits):
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(i):
if i == len(digits):
result.append("".join(path)) # L1: O(n) join when complete
return
for ch in mapping[digits[i]]: # L2: loop over letters for digit i
path.append(ch) # L3: O(1) push
backtrack(i + 1) # L4: recurse
path.pop() # L5: O(1) pop
backtrack(0)
return result

Where the time goes, line by line

Variables: n = len(digits), k = average letters per digit (3 or 4).

LinePer-call costTimes executedContribution
L1 (join + append)O(n)O(n)k^n leavesO(knn)O(k^n · n)
L2 (letter loop)O(1)O(1)k^n totalO(kn)O(k^n)
L3/L4/L5 (push/recurse/pop)O(1)O(1)k^n · nO(knn)O(k^n · n) ← dominates

The recursion tree has k^n leaves (one per combination) and n levels. Most work is at the leaves.

Complexity

  • Time: O(4nn)O(4^n · n), driven by L1/L4 building k^n combinations of length n.
  • Space: O(n)O(n) recursion + output.

Try this approach:

idle
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).
final class Solution {
func letterCombinations(_ digits: String) -> [String] {
let map: [Character: [Character]] = ["2": Array("abc"), "3": Array("def"), "4": Array("ghi"), "5": Array("jkl"), "6": Array("mno"), "7": Array("pqrs"), "8": Array("tuv"), "9": Array("wxyz")]
let values = Array(digits); if values.isEmpty { return [] }; var result: [String] = []
func search(_ index: Int, _ current: [Character]) { if index == values.count { result.append(String(current)); return }; for letter in map[values[index]]! { search(index + 1, current + [letter]) } }
search(0, []); return result
}
}

Approach 3: itertools.product

Python one-liner using the standard library.

from itertools import product
def letter_combinations(digits):
if not digits:
return []
mapping = {"2":"abc","3":"def","4":"ghi","5":"jkl",
"6":"mno","7":"pqrs","8":"tuv","9":"wxyz"}
return ["".join(p) for p in product(*(mapping[d] for d in digits))] # L1: O(k^n · n)

Where the time goes, line by line

Variables: n = len(digits), k = average letters per digit (3 or 4).

LinePer-call costTimes executedContribution
L1 (product + join)O(n)O(n) per combok^nO(knn)O(k^n · n) ← dominates

Complexity

  • Time: O(4nn)O(4^n · n).
  • Space: O(4nn)O(4^n · n).

Production-correct; rarely accepted in an interview that wants the algorithm.

final class Solution {
func letterCombinations(_ digits: String) -> [String] {
let map: [Character: [String]] = ["2": ["a","b","c"], "3": ["d","e","f"], "4": ["g","h","i"], "5": ["j","k","l"], "6": ["m","n","o"], "7": ["p","q","r","s"], "8": ["t","u","v"], "9": ["w","x","y","z"]]
if digits.isEmpty { return [] }
return digits.reduce([""]) { partial, digit in partial.flatMap { prefix in map[digit]!.map { prefix + $0 } } }
}
}

Summary

ApproachTimeSpace
Iterative cartesian productO(4nn)O(4^n · n)O(4nn)O(4^n · n)
Backtracking DFSO(4nn)O(4^n · n)O(n)O(n) recursion
itertools.productO(4nn)O(4^n · n)O(4nn)O(4^n · n)

All optimal in time. Backtracking is smallest in recursion-stack terms and is the “show you understand the algorithm” interview answer.

Test cases

def letter_combinations(digits):
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(i):
if i == len(digits):
result.append("".join(path))
return
for ch in mapping[digits[i]]:
path.append(ch)
backtrack(i + 1)
path.pop()
backtrack(0)
return result
def _run_tests():
assert sorted(letter_combinations("23")) == sorted(["ad","ae","af","bd","be","bf","cd","ce","cf"])
assert letter_combinations("") == []
assert sorted(letter_combinations("2")) == ["a","b","c"]
# digit 7 has 4 letters: pqrs
assert sorted(letter_combinations("7")) == ["p","q","r","s"]
# two-digit: 2+2 = 9 combinations
assert len(letter_combinations("22")) == 9
print("all tests pass")
if __name__ == "__main__":
_run_tests()
  • Permutations, the ordered arrangement pattern where position and used items define the search.
  • Backtracking, the explore, undo, and prune pattern for building candidates.