Backtracking · Medium
LeetCode 17: Letter Combinations of a Phone Number — solution
Classic backtracking: pick one letter per digit, recurse to the next digit, and record the string when every digit is used. O(4^n * n) time for n digits and O(n) extra space besides the output.
Problem in plain words
Old phone keypads map each digit from 2 to 9 to a few letters (2 is abc, 7 is pqrs, 9 is wxyz, and so on). Given a string of such digits, list every word you could spell by choosing one letter for each digit, in order. If the input is empty, return an empty list.
For example, "34" maps to def and ghi, giving nine strings: dg, dh, di, eg, eh, ei, fg, fh, fi.
Intuition
Think of a decision tree. The first level chooses a letter for the first digit, the second level chooses a letter for the second digit, and so on. Every path from the root to a leaf is one answer. Walking this tree depth-first while keeping a single growing string is exactly backtracking: append a letter, explore deeper, then remove the letter and try the next one. It is the same choose / explore / un-choose rhythm used in the N-Queens lesson, just without any pruning because every path is valid.
Approach
- If
digitsis empty, return an empty list right away. - Store the keypad as an array indexed by digit: index 2 holds
abc, index 9 holdswxyz. - Write
backtrack(index, path). Ifindex == digits.length, addpathto the result and return. - Otherwise, for each letter mapped to
digits[index], append it topath, callbacktrack(index + 1, path), and then remove the last character. - Start with
backtrack(0, empty)and return the collected list.
Dry run
Input: "29", where 2 is abc and 9 is wxyz.
- Choose
afor digit 2. Then try each letter of 9: recordaw,ax,ay,az. Removea. - Choose
b: recordbw,bx,by,bz. Removeb. - Choose
c: recordcw,cx,cy,cz. - Total: 3 * 4 = 12 strings, produced in lexicographic order because the letters are tried in order.
Java solution
class Solution {
private static final String[] KEYS = {
"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"
};
public List<String> letterCombinations(String digits) {
List<String> result = new ArrayList<>();
if (digits.isEmpty()) return result;
backtrack(digits, 0, new StringBuilder(), result);
return result;
}
private void backtrack(String digits, int index, StringBuilder path, List<String> result) {
if (index == digits.length()) {
result.add(path.toString());
return;
}
String letters = KEYS[digits.charAt(index) - '0'];
for (char ch : letters.toCharArray()) {
path.append(ch); // choose
backtrack(digits, index + 1, path, result); // explore
path.deleteCharAt(path.length() - 1); // un-choose
}
}
}Python solution
class Solution:
def letterCombinations(self, digits: str) -> List[str]:
if not digits:
return []
keys = ["", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"]
result = []
path = []
def backtrack(index):
if index == len(digits):
result.append("".join(path))
return
for ch in keys[int(digits[index])]:
path.append(ch) # choose
backtrack(index + 1) # explore
path.pop() # un-choose
backtrack(0)
return resultComplexity
| Measure | Cost | Why |
|---|---|---|
| Time | O(4^n * n) | Up to 4 choices per digit, and building each finished string costs O(n). |
| Space | O(n) | Recursion depth and the path buffer; the output itself holds up to 4^n strings. |
Edge cases and common mistakes
- Empty input: the expected answer is
[], not[""]. Without the early return, the base case would add one empty string. - Digits 7 and 9 have four letters: hard-coding three letters per key silently drops answers.
- Forgetting to un-choose: if you append without removing, the path keeps growing and later answers are corrupted.
- String concatenation in Java: passing
path + chworks but creates many temporary strings; a singleStringBuilderis cleaner.
›Is there an iterative version?
Yes. Start with a list containing the empty string, and for each digit replace the list with every existing string extended by every letter of that digit. It does the same work breadth-first.
