Problem overview
The function letterCombinations receives a string digits made of the characters '2' to '9' and returns every string those keys could spell on a phone keypad. Digit 2 stands for abc, 3 for def, and so on up to 9 for wxyz. A combination takes one letter from each digit's key, in the order of the digits, and the answer lists them in keypad order: all combinations that start with the first letter of the first key come first, then those that start with its second letter, and so on, with the same rule applied to each later digit.
For "23" the answer is [ad ae af bd be bf cd ce cf]: three letters for 2 times three letters for 3 gives nine combinations. For "7" it is [p q r s], since keys 7 and 9 have four letters. For an empty digits there is nothing to spell, and the function returns no combinations at all, which is not the same as one empty string.
Approach: choose one letter per digit
The number of nested loops would depend on the length of digits, which you can't write directly. Backtracking solves this with recursion. Walk through digits one position at a time and keep the letters chosen so far in a string, current. At position i, try each letter of the key for digits[i], in the key's order: add it to current and recurse on position i + 1. When i reaches the end of digits, every digit has its letter, and current is a finished combination.
Trying the letters in the key's order is what produces keypad order, so no sorting is needed. A Go string never changes, and current + string(letter) builds a new string for each call. That means nothing has to be removed from current when a call returns, which is the undo step you often see in backtracking code.
Tracing "23"
The first call has i = 0 and current = "". For digit 2 it tries a, b and c. The call with current = "a" reaches digit 3 and tries d, e and f, saving ad, ae, af. Then the branch for b saves bd, be, bf, and the branch for c saves cd, ce, cf. Running this gives [ad ae af bd be bf cd ce cf].
Step by step
- Create
resultas an empty[]string. It collects the combinations. - Return early for empty input. If
len(digits) == 0, returnresult. Without this check the search would save the empty string as one combination. - Build
keypad, a[]stringindexed by digit. Places 0 and 1 hold empty strings, which keeps every other key at its own index:keypad[7]is"pqrs". - Start the search with
backtrack(digits, 0, "", keypad, &result): position 0, no letters chosen, and the address ofresult. - Return
result, which now holds every combination. - Write the helper
backtracknext to the main function. It takes the digits, the positioni,current, the keypad and a pointer to the result slice, and returns nothing. - Handle the end. When
i == len(digits), every digit has a letter. - Save the combination. Append
currentto*result, store the slice back, and return. - Find this digit's letters.
digits[i]-'0'converts the character into its number, which is the index intokeypad. - Loop over the letters with
range, in order. - Recurse with
i+1andcurrent+string(letter).
The solution in Go
func letterCombinations(digits string) []string {
result := []string{}
if len(digits) == 0 {
return result
}
keypad := []string{"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"}
backtrack(digits, 0, "", keypad, &result)
return result
}
func backtrack(digits string, i int, current string, keypad []string, result *[]string) {
if i == len(digits) {
*result = append(*result, current)
return
}
letters := keypad[digits[i]-'0']
for _, letter := range letters {
backtrack(digits, i+1, current+string(letter), keypad, result)
}
}
The Go details worth a closer look:
- Why a pointer to the slice.
appendmay return a slice with a new backing array. Ifbacktrackreceived a plain[]string, itsappendwould only change its own copy of the slice header, andletterCombinationswould not see the new elements. Withresult *[]stringthe helper writes the new slice through*result. - Bytes and runes.
digits[i]is a byte, such as 55 for'7'. The digit characters are consecutive, so subtracting'0'gives 7. Ranging over a string yields runes, soletteris a rune (97 for'a'), andstring(letter)turns it back into a one-letter string. - Indexing with the byte. The subtraction matters. Using
digits[i]directly as an index would look at position 50 for'2', and a ten-element slice panics withindex out of range [50] with length 10. - Immutable strings.
current + string(letter)creates a new string and leavescurrentuntouched, so each letter of the loop starts from the same prefix.
Complexity
Time: O(n · 4^n) for n digits. A key has at most four letters, so there are at most 4^n combinations, and building each one costs about n steps for the string concatenations.
Space: O(n) beyond the answer itself. The recursion is n calls deep, and each holds a string of at most n letters.
Edge cases and common mistakes
- Empty input.
letterCombinations("")returns[], with length 0. Without the early return, the call tobacktrackwould reachi == len(digits)immediately and return[""], a list with one empty string. - Four-letter keys.
"79"gives 4 × 4 = 16 combinations, starting withpw px py pzand ending withsx sy sz. Four digits of 7 and 9, such as"7979", give 256. - Mixed lengths.
"2345"gives 3 × 3 × 3 × 3 = 81 combinations."92"gives[wa wb wc xa xb xc ya yb yc za zb zc]: the first digit changes slowest. - A missing
-'0'. The index would be the character code, not the digit, and the program would panic. - Not saving the slice back. Calling
append(*result, current)without assigning the result discards the combination.
Testing
"23"gives[ad ae af bd be bf cd ce cf].""gives[]."7"gives[p q r s]."92"gives[wa wb wc xa xb xc ya yb yc za zb zc]."79"gives 16 combinations, and"2345"gives 81.
