Problem overview
The method letterCombinations receives a string digits made of the characters '2' to '9' and returns a vector<string> with every string those digits could spell on a phone keypad. Each digit stands for the letters on its key: 2 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.
The answer must be in keypad order: all combinations that start with the first letter of the first key come first, then those with its second letter, and inside each group the same rule applies to the next digit. For "23" the result has 3 × 3 = 9 strings: ["ad","ae","af","bd","be","bf","cd","ce","cf"]. With "7" it is ["p","q","r","s"], because key 7 has four letters. An empty digits gives an empty vector, not a vector holding one empty string.
Approach: choose one letter per position
The number of nested loops would have to match the number of digits, and that number isn't known in advance. Recursion solves this: one call handles one position and loops over the letters of that digit's key. For each letter it calls itself for the next position.
The call keeps the letters chosen so far in a string current. When the position reaches the end of digits, every digit has a letter and current is a complete combination, so the call saves it. Trying the letters in the key's order is enough to produce the keypad order, because the first digit's choice changes the slowest and the last digit's choice the fastest.
This is backtracking: build a partial answer, extend it in every possible way, and return to the earlier choice to try the next. Here nothing has to be undone after a call, because each call receives its own longer string (current + letter) and the caller's current is untouched.
For "79" the result has 16 strings, starting with "pw", "px", "py", "pz" and ending with "sz".
Step by step
- Create the result. An empty
vector<string>namedresultcollects the combinations. - Return early for empty input. With no digits the search below would reach its end at once and save the empty string as if it were a combination. Returning the empty
resultis the answer the problem asks for. - Build the keypad table. A
vector<string>indexed by digit holds each key's letters. The entries for 0 and 1 are empty strings; they never appear in the input, but they keep every other key at its own index. - Start the search with position 0, an empty
current, the keypad and the result. - Return the result. The helper added to the same vector, so it now holds everything.
- Declare the helper.
backtrackreturnsvoidand takesdigits, the positioni, the stringcurrent, the keypad and the result. The reference solution takes the vectors anddigitsby reference andcurrentby value. - Test for the end. When
iequalsdigits.size(), no digit is left to spell. - Save and return. Push
currentintoresult;push_backstores a copy, so later calls can't change it. - Look up the letters.
digits[i] - '0'turns the character into the number, which indexes the keypad. - Loop over those letters with a range-based
for, which visits a string's characters in order. - Recurse. Call
backtrackfor positioni + 1withcurrent + letter.
The solution in C++
class Solution {
public:
vector<string> letterCombinations(string digits) {
vector<string> result;
if (digits.empty()) {
return result;
}
vector<string> keypad = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
backtrack(digits, 0, "", keypad, result);
return result;
}
void backtrack(string& digits, int i, string current, vector<string>& keypad, vector<string>& result) {
if (i == digits.size()) {
result.push_back(current);
return;
}
string letters = keypad[digits[i] - '0'];
for (char letter : letters) {
backtrack(digits, i + 1, current + letter, keypad, result);
}
}
};
The C++ details worth a closer look:
- References versus copies.
vector<string>& resultmakes the helper append to the caller's vector. A plainvector<string>parameter would be a copy, and everything added to it would be lost when the call ended.currenthas no&on purpose: each call needs its own string. digits[i] - '0'. Indexing a string gives achar. The digit characters are consecutive, so subtracting'0'gives the digit's value:'7' - '0'is 7. Usingdigits[i]directly as the index would read far past the end of the 10-element vector, which is undefined behaviour.current + letter. Adding acharto astringbuilds a new string with that letter at the end. It is a temporary, passed by value, so the loop's next iteration starts again from the unchangedcurrent.- Signed and unsigned.
digits.size()is unsigned, andiis anint. The comparison works for small non-negative values like these, and compilers may warn about mixing the two. - The empty-string argument. The literal
""converts to astringfor the by-valuecurrent. The first parameter is a non-const reference, which is why the call passes the variabledigitsand not a literal.
Complexity
Time: O(n · 4^n) for n digits. Each key has at most 4 letters, so there are at most 4^n combinations, and building and copying each takes about n steps.
Space: O(n) besides the answer. The recursion is n calls deep, and each call holds a string of at most n letters. The result itself holds all the combinations.
Edge cases and common mistakes
- Empty input. Without the
digits.empty()check, the empty input returns a vector holding one empty string (size 1, a single"") instead of an empty vector. - One digit.
"7"gives["p","q","r","s"]and"2"gives["a","b","c"]: a key can have three or four letters. - Repeated digits.
"9999"gives 256 strings, 4^4, from"wwww"to"zzzz". That is the largest input the constraints allow. - Forgetting
- '0'. The character'2'has the code 50, not 2, so the lookup leaves the keypad vector. - Passing
resultby value. The code compiles and runs, but the caller's vector stays empty. - Wrong keypad entries. The keypad needs the two empty entries at the front. Without them,
keypad[2]would hold the letters of 4.
Testing
"23"gives 9 strings:["ad","ae","af","bd","be","bf","cd","ce","cf"].""gives[]."7"gives["p","q","r","s"]."234"gives 27 strings, from"adg"to"cfi"."79"gives 16 strings, from"pw"to"sz"."9999"gives 256 strings.
