一、 题目来源与描述
题目来源:LeetCode 第 17 题 - 电话号码的字母组合 (Medium)
题目描述 :
给定一个仅包含数字
2-9的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下(与电话按键相同)。注意
1不对应任何字母。
cpp2: abc 3: def 4: ghi 5: jkl 6: mno 7: pqrs 8: tuv 9: wxyz示例:
输入:
digits = "23"输出:
["ad","ae","af","bd","be","bf","cd","ce","cf"]
二、 核心算法:回溯法(Backtracking / DFS)
1. 思路解析
这道题本质上是一道排列组合问题。每一个数字对应多个字母,我们需要从每个数字对应的字母集合中挑选一个字母,拼接成一个完整的字符串。
由于字符串的长度不定(最多为 4),且需要枚举所有可能的组合,暴力循环(嵌套 for 循环)无法动态适应长度 。因此,我们需要使用回溯算法(深度优先搜索 DFS)。
回溯法的核心思想:
将问题抽象为一棵决策树(N 叉树)。
树的深度 :由输入字符串
digits的长度决定。节点的分支:由当前数字对应的字母数量决定(3 或 4 个分支)。
我们通过递归沿着树的深度遍历,当到达叶子节点(即处理完所有数字)时,记录当前路径,然后"回溯"到上一层,尝试其他分支。
2. 回溯三步曲
确定递归函数的参数与返回值:
参数需要包含:给定的数字字符串
digits,以及一个索引index用来标记当前遍历到了哪一个数字。返回值:不需要返回值,结果直接存入全局变量(或类成员变量)中。
确定终止条件:
- 当
index == digits.size()时,说明已经处理完了所有的数字,此时将当前拼接好的字符串加入结果集,并结束本层递归。确定单层遍历逻辑:
获取当前
index指向的数字对应的字母集letters。使用
for循环遍历letters:
将当前字母加入当前路径
path。递归调用下一层,
index + 1。回溯操作 :将当前字母从
path中移除,以便尝试下一个字母。
三、 代码实现 (C++)
cpp
class Solution {
public:
// 1. 建立数字到字母的映射表 (使用数组下标直接映射,效率极高)
const string phone_map[10] = {
"", // 0
"", // 1
"abc", // 2
"def", // 3
"ghi", // 4
"jkl", // 5
"mno", // 6
"pqrs", // 7
"tuv", // 8
"wxyz" // 9
};
// 存放最终结果和当前路径的成员变量
vector<string> result;
string current_path;
// 2. 回溯函数
void backtracking(const string& digits, int index) {
// 终止条件:index 走到头了,说明生成了一个完整组合
if (index == digits.size()) {
result.push_back(current_path);
return;
}
// 获取当前数字对应的字符集
int digit = digits[index] - '0'; // 字符转整数
string letters = phone_map[digit];
// 遍历当前数字对应的所有字母
for (int i = 0; i < letters.size(); i++) {
current_path.push_back(letters[i]); // 处理节点:加入
backtracking(digits, index + 1); // 递归:进入下一层
current_path.pop_back(); // 回溯:撤销处理
}
}
// 3. 主函数
vector<string> letterCombinations(string digits) {
// 每次调用前清空全局状态
result.clear();
current_path.clear();
// 特殊情况:如果输入为空,直接返回空数组
if (digits.empty()) {
return result;
}
backtracking(digits, 0);
return result;
}
};

四、 复杂度分析
时间复杂度:O(3M×4N)
其中M是输入中对应 3 个字母的数字个数(例如 2, 3, 4, 5, 6, 8),N是输入中对应 4 个字母的数字个数(例如 7, 9),M+N 是输入数字的总个数。
一共有 3M×4N种组合,每种组合需要 O(1)的时间拼接字符串(在 C++ 中 push_back 和 pop_back 均摊为 O(1))。
空间复杂度:O(M+N)
主要取决于递归调用栈的深度,最大深度为输入字符串的长度(即 M+N)。此外,结果集 result 的空间不计入算法的辅助空间复杂度。
五、 递归与回溯模板总结
本题是标准的回溯模板:
cpp
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果;
}
}