【保姆级题解】力扣17. 电话号码的字母组合 (回溯算法经典入门) | Python/C/C++多语言详解

很多刚接触算法的小伙伴,一听到"回溯"、"递归"就觉得头大。别怕!这道题是绝佳的入门练习。我会把思考过程掰开揉碎,用画树状图的方式,带你一步步拿下它。

一、 题目剖析与思路解析

题目大意: 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。其实就是老式手机键盘的九宫格拼音输入法。

核心思路:把组合问题转化成"树的遍历"!

假设输入的是 "23"

  • 数字 2 对应字母串 "abc"

  • 数字 3 对应字母串 "def"

这就相当于我们要在这个树形结构里,找出所有从根节点到叶子节点的路径:

复制代码
               (开始)
             /   |   \
  数字2:    a    b    c
           /| \  ...  ...
  数字3:  d e f
  • 路径1:a -> d (组合 "ad")

  • 路径2:a -> e (组合 "ae")

  • ...以此类推。

既然是收集所有路径,回溯算法就是不二之选!接下来我们祭出经典的"回溯三部曲"。

二、 回溯三部曲

我们要写一个递归函数 backtrack,在写之前,先明确三个问题:

  1. 确定回溯函数参数: 我们需要传入当前的数字字符串 digits,还需要一个参数 index 来记录当前遍历到了第几个数字。

  2. 确定终止条件:

    什么时候收集结果?当 index 等于 digits 的长度时,说明所有的数字都遍历完了,这时候把手里拼好的字符串存入结果集,然后 return

  3. 确定单层遍历逻辑:

    拿到当前数字,查表找出它对应的字母串。然后用一个 for 循环遍历这些字母。选中一个字母,接着递归处理下一个数字(index + 1),递归回来后,要把刚才选的字母撤销掉(回溯),换下一个字母继续试。

为了方便查找数字对应的字母,我们首先需要定义一个映射表(数组或哈希表均可)。


三、 代码实现与逐行注释

这里提供 Python、C++ 和 C 三种语言的解法,大家可以选择自己熟悉的语言食用。

1. Python 解法 (最简洁直观)

Python 的字符串操作非常方便,代码看起来最清爽。

python 复制代码
class Solution:
    def __init__(self):
        # 1. 定义数字到字母的映射表 (0和1不对应字母,留空)
        self.letter_map = [
            "",     # 0
            "",     # 1
            "abc",  # 2
            "def",  # 3
            "ghi",  # 4
            "jkl",  # 5
            "mno",  # 6
            "pqrs", # 7
            "tuv",  # 8
            "wxyz"  # 9
        ]
        self.result = [] # 存放最终的所有组合
        self.path = ""   # 存放当前正在拼接的单个组合

    def letterCombinations(self, digits: str) -> list[str]:
        if not digits:
            return [] # 如果输入为空,直接返回空列表
        
        self.result.clear() 
        self.backtrack(digits, 0)
        return self.result

    # 回溯函数
    def backtrack(self, digits: str, index: int):
        # 2. 终止条件:当 index 等于 digits 长度时,说明走到了叶子节点
        if index == len(digits):
            self.result.append(self.path) # 收集结果
            return

        # 3. 单层遍历逻辑
        digit = int(digits[index])      # 取出当前字符并转为整数,例如 '2' -> 2
        letters = self.letter_map[digit] # 查表拿到对应的字母串,例如 "abc"

        for letter in letters:
            self.path += letter                  # 处理:把当前字母拼接到 path
            self.backtrack(digits, index + 1)    # 递归:处理下一个数字
            self.path = self.path[:-1]           # 回溯:撤销刚才添加的字母,尝试下一个

2. C++ 解法 (算法面试最常用)

C++ 使用 std::vectorstd::string,效率高且标准。

cpp 复制代码
#include <vector>
#include <string>

using namespace std;

class Solution {
private:
    // 1. 映射表,使用 const 数组更加高效
    const string letterMap[10] = {
        "",     // 0
        "",     // 1
        "abc",  // 2
        "def",  // 3
        "ghi",  // 4
        "jkl",  // 5
        "mno",  // 6
        "pqrs", // 7
        "tuv",  // 8
        "wxyz"  // 9
    };

    vector<string> result; // 存放最终结果
    string path;           // 存放当前路径

    // 回溯函数
    void backtracking(const string& digits, int index) {
        // 2. 终止条件:路径长度等于输入数字的长度
        if (index == digits.size()) {
            result.push_back(path); // 收集当前组合
            return;
        }

        // 3. 单层遍历逻辑
        int digit = digits[index] - '0';  // 将字符转为数字,比如 '2' - '0' = 2
        string letters = letterMap[digit]; // 拿到对应的字母集

        for (int i = 0; i < letters.size(); i++) {
            path.push_back(letters[i]);       // 处理:加入当前字母
            backtracking(digits, index + 1);  // 递归:往下一层走
            path.pop_back();                  // 回溯:弹出末尾字母,准备尝试下一个
        }
    }

public:
    vector<string> letterCombinations(string digits) {
        result.clear();
        path.clear();
        if (digits.empty()) {
            return result; // 判空处理
        }
        backtracking(digits, 0);
        return result;
    }
};

3. C 语言解法 (考验底层基本功)

C 语言没有自带的字符串类和动态数组,需要我们手动分配内存 (malloc),非常锻炼基本功。对于正在做系统级实验或者打牢基础的同学来说,这版代码很有参考价值。

objectivec 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

// 1. 全局映射表
const char* letterMap[10] = {
    "", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"
};

char** result;      // 存储最终结果的二维数组
int resultTop;      // 记录结果集里的元素个数
char* path;         // 记录当前的组合路径

// 回溯函数
void backtrack(char* digits, int index) {
    // 2. 终止条件
    if (index == strlen(digits)) {
        // 遇到终止条件,将当前 path 拷贝并存入 result
        char* temp = (char*)malloc(sizeof(char) * (strlen(digits) + 1));
        strcpy(temp, path);
        result[resultTop++] = temp;
        return;
    }

    // 3. 单层遍历逻辑
    int digit = digits[index] - '0';        // 字符转整数
    const char* letters = letterMap[digit]; // 获取对应字母串
    int len = strlen(letters);

    for (int i = 0; i < len; i++) {
        path[index] = letters[i];     // 处理:直接在对应索引处赋值
        path[index + 1] = '\0';       // 保持字符串有结束符
        
        backtrack(digits, index + 1); // 递归
        
        // 注意:C语言这里通过下一次循环覆盖 path[index] 来实现"回溯",
        // 不需要显式的 pop 操作,非常巧妙!
    }
}

char ** letterCombinations(char * digits, int* returnSize) {
    *returnSize = 0; // 初始化返回的数组大小为0
    int len = strlen(digits);
    if (len == 0) return NULL; // 为空直接返回

    // 估算最大可能的组合数:每个数字最多对应4个字母,最大长度是4^N
    int maxCombinations = pow(4, len); 
    
    // 分配内存
    result = (char**)malloc(sizeof(char*) * maxCombinations);
    path = (char*)malloc(sizeof(char) * (len + 1));
    resultTop = 0;
    path[0] = '\0'; // 初始化为空字符串

    // 开启回溯
    backtrack(digits, 0);

    *returnSize = resultTop; // 设置返回的实际数组大小
    return result;
}

四、 复杂度分析

无论使用哪种语言,算法的本质是一样的:

时间复杂度: O(n4^n),其中 n 为 digits 的长度。最坏情况下每次需要枚举 4 个字母,递归次数为一个满四叉树的节点个数,那么一共会递归 O(4^n) 次(等比数列和),再算上加入答案时复制 path 需要 O(n) 的时间,所以时间复杂度为 O(n4^n)。
**空间复杂度:**O(n)。返回值的空间不计入


总结: 回溯算法本质上就是一种暴力穷举,只是套上了一层递归的壳子。只要按照"回溯三部曲"的框架去思考,理清参数、终止条件和单层逻辑,再难的题目也能被拆解得明明白白。

照例附上卡哥的代码随想录

17.电话号码的字母组合 | 回溯算法 | 数字映射 | 字符集 | 代码随想录

最后这个复杂度分析抄的灵神的,我自己还是不太会分析,灵神B站题目讲解:

回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili

相关推荐
2301_777599371 小时前
Go语言如何做HTTP连接池_Go语言HTTP连接池教程【最新】
jvm·数据库·python
脱氧核糖核酸__1 小时前
LeetCode热题100——238.除了自身以外数组的乘积(题目+题解+答案)
数据结构·c++·算法·leetcode
再卷也是菜1 小时前
算法提高篇(1)线段树(上)
数据结构·算法
py有趣1 小时前
力扣热门100题之单词拆分
算法·leetcode
ouliten1 小时前
C++笔记:std::invoke
c++·笔记
坐吃山猪1 小时前
Python27_协程游戏理解
开发语言·python·游戏
Polar__Star1 小时前
Redis如何利用位图快速判断数据存在性
jvm·数据库·python
2301_817672262 小时前
CSS如何实现优雅的间距_使用CSS Grid控制盒模型间隙
jvm·数据库·python
你说咋整就咋整2 小时前
openGauss6.0.3 一主二从集群安装手册
数据库·python·gaussdb