A complete visual walkthrough of the backtracking approach โ from phone keypad to recursive tree.
Each digit on a phone maps to a set of letters. Given a string of digits, we must return every possible letter combination those digits could represent.
Digits 2-9 each map to 3 or 4 letters. Digit 1 maps to nothing.
2 โ abc, 3 โ def. Every letter from "2" paired with every letter from "3" gives 3 ร 3 = 9 combinations.
For two digits, we could write a double-nested loop. For three digits, a triple-nested loop. But the number of digits is variable (0 to 4), so we cannot hardcode the nesting depth.
// For digits = "23" โ works fine with 2 loops for (char c1 : "abc".toCharArray()) for (char c2 : "def".toCharArray()) result.add("" + c1 + c2); // For digits = "234" โ need 3 loops for (char c1 : "abc".toCharArray()) for (char c2 : "def".toCharArray()) for (char c3 : "ghi".toCharArray()) result.add("" + c1 + c2 + c3); // For digits = "2345" โ need 4 loops... // How many loops for n digits? We can't know at compile time!
The problem: We need a variable number of nested loops, one per digit. This is a classic signal that we need recursion. Each recursive call acts as one "level" of nesting, and the recursion depth adapts to the input size automatically.
We build each combination one character at a time. At each digit position, we try every letter that digit maps to, recurse deeper for the next digit, then undo our choice (backtrack) before trying the next letter.
This creates a decision tree where each level corresponds to a digit, and each branch to a letter choice. For digits = "23":
Each path from root to leaf is one complete combination. The tree has 3 ร 3 = 9 leaves = 9 combinations.
Backtracking is DFS on this decision tree. We go deep-first: build "a" โ "ad" (record it), backtrack to "a" โ "ae" (record it), backtrack to "a" โ "af" (record it), then backtrack all the way to root and try "b". The StringBuilder.deleteCharAt call is the "undo" step.
Let's trace every recursive call for digits = "23". Watch the path build up and get undone:
The StringBuilder acts as a shared, mutable path. We append before recursing and delete after returning. This is more efficient than creating new String objects at each level, and it is the standard backtracking pattern in Java.
The problem has a few important boundary conditions to handle:
The empty-check guard is critical. Without if (digits.isEmpty()) return result, we would add an empty string "" to the result when the input is empty. The problem specifies we should return an empty list, not a list containing one empty string.
class Solution { // Index 0 and 1 are empty โ digits 0 and 1 have no letters private static final String[] MAPPING = { "", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz" }; public List<String> letterCombinations(String digits) { List<String> result = new ArrayList<>(); // Guard: empty input returns empty list (not [""]) if (digits.isEmpty()) return result; // Start backtracking from digit index 0 backtrack(digits, 0, new StringBuilder(), result); return result; } private void backtrack(String digits, int index, StringBuilder path, List<String> result) { // Base case: built a complete combination if (index == digits.length()) { result.add(path.toString()); // snapshot the path return; } // Get letters for current digit String letters = MAPPING[digits.charAt(index) - '0']; for (char c : letters.toCharArray()) { path.append(c); // 1. Choose backtrack(digits, index + 1, // 2. Explore path, result); path.deleteCharAt( // 3. Un-choose path.length() - 1); } } }
Enter digits and watch the backtracking tree build up step by step. Each step shows the algorithm choosing a letter, exploring deeper, or backtracking.
Each digit maps to at most 4 letters (digits 7 and 9). With n digits, the tree has at most 4^n leaves (complete combinations). At each leaf we call path.toString() which copies n characters, giving us the extra factor of n.
The recursion depth is n (one frame per digit). The StringBuilder path also holds at most n characters. We do not count the output list in auxiliary space โ it is required to store the answer.
In practice, n is at most 4 (constraint: 0 โค digits.length โค 4). So the maximum number of combinations is 4^4 = 256. The asymptotic analysis matters for understanding the algorithm, but this problem's constraints make it trivially fast for any approach.