Description
給定一個包含 2-9 的數字字串,並根據電話按鍵的圖片,將數字對應到英文字母,回傳所有可能的英文字串之排列組合。

Example 1:
Input: digits = "23"
Output: ["ad","ae","af","bd","be","bf","cd","ce","cf"]
Example 2:
Input: digits = ""
Output: []
Example 3:
Input: digits = "2"
Output: ["a","b","c"]
Solution
本題算是相對簡單的 Medium 題目,首先我們可以根據電話按鍵建構一個 mapping table,利用一個長度為 8 的 vector array,紀錄對應的英文字母:[{'a', 'b', 'c'}, {'d', 'e', 'f'}, ...],例如當我們發現 digit 為 d = '3',可以利用 d 與 '2' 的距離,找到對應的英文字母 table[d - '2']。接下來我們可以利用 for 迴圈或是 recursion 的方式求得答案。
Solution 1 – for loop
以下說明如何用 for 迴圈解題,假設我們得到 digits = "23",我們需要兩個 vector 容器 vector<string> ans 以及 vector<string> tmp,分別保存目前的所有組合,以及暫時紀錄加上下個 digit 的所有組合。在初始狀態時我們必須先將空字串 "" 放入 vector<string> ans 內,再開始 for 迴圈。
每次的迴圈步驟都會相同。首先我們會持續迭代得到下一個 digit,以我們的例子 d = '2',可以得到對應的字母 table[d - '2'] = {'a', 'b', 'c'}。我們的目標是要將 ans 裡面所有的字串方別加上 {'a', 'b', 'c'},要做到這個目標我們需要兩個 for 迴圈,第一個 for 迭代 ans 裡現有的字串,第二個 for 則是依序迭代對應的英文字母 {'a', 'b', 'c'},我們把從 ans 讀出的字串以及某個英文字母串接,並將這個結果推入另一個暫時的容器 vector<string> tmp,此時我們可以得到 tmp = {"a", "b", "c"}。最後我們將暫時的容器寫回答案 ans = move(tmp),注意這裡可以利用 std::move 做移動的動作,避免多次 copy construct 導致效能降低,或是增加記憶體使用量。接下來我們繼續處理下一個字母,再重複相同的步驟,直到所有字母都被我們處理過,即可跳出迴圈。

Solution 2 – recursion
除了 for 迴圈之外,我們也可以利用 recursive (DFS)的方式解題。首先我們需要有一個字串 accumulate 紀錄我們的暫時結果,並且我們需要一個 idx 紀錄我們走到第幾個 digit。我們以 digits = "234" 為例,我們從第一個 digit '2' 開始,此時 idx = 0, accumulate = "",並且有三個字母 {'a', 'b', 'c'} 需要考慮,因此在第一層 recursion 內我們需要用 for 迭代三個字母,對於這一層的每個字母 d,我們紀錄 idx += 1, accumulate += d 並且將這些紀錄送到第二層的 recursion 後,再繼續處理下一個字母並再次傳送。
在第二層的 recursion 中我們遇到三個英文字母 {'d', 'e', 'f'},我們一樣迭代每個英文字母,並且將 idx += 1, accumulate += d 送到第三層,假設第一層的 accumulate = "a" 送到這層,我們會將 accumulate = "ad"、accumulate = "ae"、accumulate = "af",搭配 idx = 2 送到下一層。
最後一層我們會遇到 三個英文字母 {'g', 'h', 'i'},重複相同的做法,假設第二層的 accumulate = "ad" 傳到這裡,我們迭代三個英文字母可以得到 accumulate = "adg"、accumulate = "adh"、accumulate = "adi"。唯一不同的是我們依據 idx 判斷出我們已經到達最後一層,所以就不再繼續送到下一層,而是將目前的 accumulate 推入我們的 vector<string> result 裡面,作為其中一個答案。

完整程式碼如下:
// Solution 1 - for loop
class Solution {
public:
vector<string> letterCombinations(string digits) {
if (digits.size() == 0) return vector<string>();
vector<vector<char>> phone_map = {
{'a', 'b', 'c'},
{'d', 'e', 'f'},
{'g', 'h', 'i'},
{'j', 'k', 'l'},
{'m', 'n', 'o'},
{'p', 'q', 'r', 's'},
{'t', 'u', 'v'},
{'w', 'x', 'y', 'z'}
};
vector<string> ans = {""};
vector<string> tmp;
for (auto &d: digits)
{
for (auto &s: ans)
{
for (auto &c: phone_map[d - '2'])
{
tmp.push_back(s + c);
}
}
ans = move(tmp);
tmp = {};
}
return ans;
}
};
//Solution 2 - recursion
class Solution {
public:
vector<string> letterCombinations(string digits) {
if (digits.size()==0)
{
return vector<string>();
}
vector<char> table[8];
table[0] = {'a', 'b', 'c'};
table[1] = {'d', 'e', 'f'};
table[2] = {'g', 'h', 'i'};
table[3] = {'j', 'k', 'l'};
table[4] = {'m', 'n', 'o'};
table[5] = {'p', 'q', 'r', 's'};
table[6] = {'t', 'u', 'v'};
table[7] = {'w', 'x', 'y', 'z'};
vector<string> answer;
backtrack(digits, table, 0, answer, "");
return answer;
}
void backtrack(const string &digits, const vector<char> (&table)[8], int idx, vector<string> &result, string accumulate)
{
for (auto it=table[digits[idx] - '2'].begin(); it!=table[digits[idx] - '2'].end(); ++it)
{
if (idx==digits.size() - 1)
{
result.push_back(accumulate + *it);
}
else
{
backtrack(digits, table, idx + 1, result, accumulate + *it);
}
}
}
};
Pingback: LeetCode 系列文章總整理 - PlayRound 玩轉部落格