Letter Combinations of a Phone Number
Letter Combinations of a Phone Number
Given a digit string (2-9), return all possible letter combinations based on telephone keypad mapping. Order does not matter.
Why this is worth learning
This is a Cartesian product, not a membership search. Each digit fixes a position and supplies its allowed letters; every full tuple is an answer, so output size is an unavoidable cost.
Before coding: one-minute experiment
How many results come from digits 79, and how many bytes are written just to store those result strings?
Function signature
func LetterCombinations(digits string) []string
Inputs / outputs
digits: string of digits 2-9.- Return: all possible letter combinations.
Example
digits = "23"
Output: ["ad","ae","af","bd","be","bf","cd","ce","cf"]
Constraints
- 0 <= len(digits) <= 4
Core idea (near-solution)
Start with one empty partial string and extend every partial string with each letter of the next digit. For "27", three first-position choices each need four second-position choices: all 12 outputs are required. No search method can skip writing them. The useful saving is to share one prefix buffer while visiting the choices, copying only completed strings; the digit index already determines the next decision.
This is a Cartesian product over digit-to-letter mappings. Use backtracking to build one character at a time.
Invariant: the current path has length i and matches the first i digits.
Algorithm (step-by-step)
- If
digitsis empty, return empty slice. - Map digits to letter strings ("2" -> "abc", etc.).
- DFS with index
iand bufferpath:- If
i == len(digits), record the string. - For each letter in mapping[digits[i]], append, recurse at i+1, then restore the previous path length before the next letter.
- If
- Return the results.
Correctness sketch
At recursion depth i, the path has exactly i letters, each chosen from the corresponding digit mapping. Extending by every allowed letter preserves this invariant. At depth n the path is a legal combination and is copied into an independent string. Every possible combination has one unique sequence of choices, so it is reached once and no invalid string is emitted. Popping after each recursive call restores the prefix for the next sibling. The explicitly empty input returns an empty result rather than the mathematical Cartesian-product identity containing one empty string.
Trace (hand walkthrough)
Example input:
digits = "23"
Output: ["ad","ae","af","bd","be","bf","cd","ce","cf"]
Walk:
- digits "2" -> [a,b,c]
- extend with "3" (d,e,f) -> ad, ae, af, bd, be, bf, cd, ce, cf
Pitfalls to avoid
- Returning
[]string{""}for empty input (should be empty). - Using rune-based iteration incorrectly (letters are ASCII here).
Debugging lens
Test 7 and 9, whose mappings have four letters. Do not append a mutable path buffer by alias. Preserve the empty-input convention instead of unconditionally emitting an empty string.
Complexity
Let P be the product of the mapping sizes, with n digits. Time O(n P), including copying each length-n output; P lies between 3^n and 4^n. Auxiliary recursion/path space O(n), excluding O(n P) output storage.
Implementation notes (Go)
- Use a
[]bytebuffer for the current path and convert to string on output.
Reconstruction checkpoint
How many results come from digits 79, and how many bytes are written just to store those result strings?
Model answer
Both digits offer four letters, so there are 16 results of length 2: at least 32 output bytes, excluding string headers. This explains the output-length factor in runtime.