题目描述
本文是LC第17题,电话号码的字母组合,题目描述如下:
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
限制
0 <= digits.length <= 4
digits[i] 是范围 [‘2’, ‘9’] 的一个数字。

示例 1:
输入:digits = “23”
输出:[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”]
示例 2:
输入:digits = “”
输出:[]
示例 3:
输入:digits = “2”
输出:[“a”,“b”,“c”]
解题思路
- 回溯法(递归)(dfs深度优先搜索)
 数据结构:哈希表,存储每个数字对应的所有可能的字母。
 算法:回溯过程中维护一个字符串,表示已有的字母序列。该字符串初始为空。
- 每次取电话号码的一位数字,从哈希表中获得该数字对应的所有可能的字母
- 将第一个字母插入到已有的字母排列后面,
- 继续处理电话号码的后一位数字,
- 直到处理完电话号码中的所有数字,即得到一个完整的字母排列。
- 进行回退操作,每次回退一个数字,假如第一次添加的是该数字的第一个字母,回退后,就是将该字母从序列末尾取出,然后将第二个字母放入末尾,形成一个新的序列。
回溯算法用于寻找所有的可行解,如果发现一个解不可行,则会舍弃不可行的解。
在这道题中,由于每个数字对应的每个字母都可能进入字母组合,因此不存在不可行的解,直接穷举所有的解即可。
class Solution {
private:
    map<char, string> map_phone = {
        {'2', "abc"},
        {'3', "def"},
        {'4', "ghi"},
        {'5', "jkl"},
        {'6', "mno"},
        {'7', "pqrs"},
        {'8', "tuv"},
        {'9', "wxyz"}
    };
public:
    vector<string> letterCombinations(string digits) 
    {
        // 判空
        if (digits == "") return {};
        vector<string> result;
        string path;
        backtrack(result, digits, 0, path);
        return result;
    }
private:
    // NOTION: 所有 值传递的 参数在递归/回溯 中都会刷新, 不会永久存储;;因此 result 参数作为保存结果的参数, 必须用引用或指针传递。
    void backtrack(vector<string>& result, string digits, int index, string path) 
    {
        if (index == digits.length()) {
            result.push_back(path);
        } else {
            string str = map_phone[digits[index]];
            for (int j = 0; j < str.length(); ++j) {
                backtrack(result, digits, index+1, path+str[j]);
            }
            
        }
    }
};
其实,backtrack的实现是核心,因此一定要写好backtrack中的循环,笔者第一次解题的时候将backtrace中的循环写成了如下代码
for (int i = index; i < digits.length(); ++i) {
     string str = map_phone[digits[i]];
     for (int j = 0; j < str.length(); ++j) {
         backtrack(result, digits, index+1, path+str[j]);
     }
}
而上述错误代码与题解中正确代码区别在于
上述代码产出的是类似如下的一颗树状结构:
        index: 0     2      3  (index=0, 遍历 2 和 3)
                    /|\    /|\
                   a b c  d e f
        index: 1  /|\ .../|\  (index=1, 只遍历3)
                 d e f  d e f
                /|\
               ad ... 
        index: 2 (没有要遍历的元素)
        res: ad ae af bd be bf cd ce cf dd de df ed ee ef fd fe ff
题解代码产生是如下的树状结构,注意辨析二者的区别
        index: 0        2
                       /|\
                      a b c
                     /|\...
        index: 1    d e f
                   /|\
                  ad...
        res: ad ae af bd be bf cd ce cf
- 双端队列(bfs广度优先搜索)
 核心思路:借助C++中的双端队列,deque,该结构是一个既可以在队列头部插入,也可以在队列尾部插入的结构。
 遍历数字,每遍历一个数字都将该数字代表的所有字符,都push到队列中已经有的每个序列的末尾,形成一个新的序列,同时将原来的旧的序列从队列头部pop掉。
// 方法二 : 双端队列 (bfs)
    vector<string> letterCombinations(string digits)
    {
        if (digits == "") return {};
        vector<string> result;
        deque<string> path_queue;
        path_queue.push_back("");
        
        for (int i = 0; i < digits.length(); ++i) {
            string str = m_map_phone[digits[i]];
            int cycle = path_queue.size();
            for (int k = 0; k < cycle; ++k) {
                string path = path_queue.front();
                path_queue.pop_front();
                for (int j = 0; j < str.length(); ++j) {
                    path_queue.push_back(path+str[j]);
                }
            }
        }
        result.assign(path_queue.begin(), path_queue.end());
        return result;
    }



















