当前位置: 首页 > news >正文

【LeetCode】17. 电话号码的字母组合

文章目录

  • 17. 电话号码的字母组合
    • 题目描述
    • 示例 1:
    • 示例 2:
    • 示例 3:
    • 提示:
    • 解题思路
      • 算法分析
      • 问题本质分析
      • 回溯法详解
      • 组合生成过程可视化
      • 数字映射关系
      • 各种解法对比
      • 算法流程图
      • 边界情况处理
      • 时间复杂度分析
      • 空间复杂度分析
      • 关键优化点
      • 实际应用场景
      • 测试用例设计
      • 代码实现要点
    • 完整题解代码

17. 电话号码的字母组合

题目描述

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

在这里插入图片描述

示例 1:

输入:digits = “23”
输出:[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”]

示例 2:

输入:digits = “”
输出:[]

示例 3:

输入:digits = “2”
输出:[“a”,“b”,“c”]

提示:

  • 0 <= digits.length <= 4
  • digits[i] 是范围 [‘2’, ‘9’] 的一个数字。

解题思路

这道题要求根据电话号码的数字组合,生成所有可能的字母组合。这是一个经典的回溯算法问题,需要枚举所有可能的组合。每个数字对应3-4个字母,需要生成所有可能的排列组合。

算法分析

这道题的核心思想是回溯枚举,主要解法包括:

  1. 回溯法:使用递归和回溯生成所有组合(推荐)
  2. 迭代法:使用队列进行层序遍历
  3. 优化版本:使用strings.Builder提高字符串操作效率
  4. BFS方法:广度优先搜索生成组合
  5. 递归分治:使用分治思想逐步构建组合

问题本质分析

graph TDA[电话号码字母组合] --> B[数字映射]B --> C[组合生成]C --> D[回溯枚举]B --> E[2→abc, 3→def, 4→ghi]B --> F[5→jkl, 6→mno, 7→pqrs]B --> G[8→tuv, 9→wxyz]C --> H[每个位置选择字母]C --> I[生成所有可能组合]D --> J[递归回溯]E --> K[映射关系建立]F --> KG --> KH --> L[组合策略]I --> LJ --> L

回溯法详解

flowchart TDA[输入digits字符串] --> B{digits为空?}B -->|是| C[返回空数组]B -->|否| D[初始化结果数组]D --> E[调用回溯函数backtrack]E --> F[backtrack(index=0, current="")]F --> G{index == len(digits)?}G -->|是| H[添加current到结果]G -->|否| I[获取当前数字对应的字母]H --> J[返回结果]I --> K[遍历每个字母]K --> L[选择当前字母]L --> M[递归调用backtrack(index+1)]M --> N[回溯:移除当前字母]N --> O{还有字母?}O -->|是| KO -->|否| P[返回上一层]G --> Q[终止条件处理]Q --> R[结果收集]

组合生成过程可视化

输入: digits = '23'
数字映射: 2→abc, 3→def
第1层: 选择2的字母
选择'a' → current='a'
选择'b' → current='b'
选择'c' → current='c'
第2层: 选择3的字母
第2层: 选择3的字母
第2层: 选择3的字母
选择'd' → 'ad'
选择'e' → 'ae'
选择'f' → 'af'
选择'd' → 'bd'
选择'e' → 'be'
选择'f' → 'bf'
选择'd' → 'cd'
选择'e' → 'ce'
选择'f' → 'cf'
最终结果: [ad,ae,af,bd,be,bf,cd,ce,cf]

数字映射关系

graph TDA[数字映射表] --> B[2 → abc]A --> C[3 → def]A --> D[4 → ghi]A --> E[5 → jkl]A --> F[6 → mno]A --> G[7 → pqrs]A --> H[8 → tuv]A --> I[9 → wxyz]B --> J[3个字母]C --> JD --> JE --> JF --> JG --> K[4个字母]H --> JI --> KJ --> L[标准按键]K --> M[扩展按键]L --> N[组合数量计算]M --> N

各种解法对比

解法对比
回溯法
迭代法
优化版本
BFS方法
递归分治
时间O_4^n空间O_n
时间O_4^n空间O_4^n
时间O_4^n空间O_n
时间O_4^n空间O_4^n
时间O_4^n空间O_n
推荐解法
队列实现
性能最优
层序遍历
分治思想
平衡性能和可读性

算法流程图

flowchart TDA[开始] --> B{digits为空?}B -->|是| C[返回空数组]B -->|否| D[初始化结果数组]D --> E[调用backtrack(0, '')]E --> F{index >= len(digits)?}F -->|是| G[添加current到结果]F -->|否| H[获取当前数字的字母]G --> I[返回结果]H --> J[遍历每个字母]J --> K[选择当前字母]K --> L[递归backtrack(index+1)]L --> M[回溯:移除字母]M --> N{还有字母?}N -->|是| JN -->|否| O[返回]F --> P[递归终止条件]P --> Q[结果收集和返回]

边界情况处理

边界情况
空字符串
单个数字
多个数字
包含7或9
返回空数组
返回对应字母数组
正常回溯处理
处理4个字母的情况
特殊情况处理

时间复杂度分析

时间复杂度分析
组合数量计算
每个数字的字母数
总体复杂度
3^n 或 4^n
大部分数字3个字母
7和9有4个字母
O_4^n
最坏情况
平均情况
指数级增长

空间复杂度分析

空间复杂度分析
递归调用栈
结果存储
总体空间复杂度
O_n
O_4^n
O_4^n
递归深度
结果数组大小

关键优化点

优化策略
字符串操作优化
数据结构选择
内存管理
使用strings.Builder
数组替代map
避免不必要的内存分配
减少字符串拼接开销

实际应用场景

应用场景
电话键盘
密码生成
组合枚举
游戏设计
手机拨号界面
密码破解工具
排列组合计算
文字游戏
核心算法组件

测试用例设计

测试用例
基础功能
边界情况
特殊情况
正常数字组合
多个数字
不同长度
空字符串
单个数字
最大长度
包含7或9
重复数字
连续数字
验证正确性
验证特殊规则

代码实现要点

  1. 回溯策略

    • 使用递归函数进行深度优先搜索
    • 在每个位置尝试所有可能的字母
    • 到达叶子节点时收集结果
  2. 状态管理

    • 维护当前构建的字符串
    • 记录当前处理的位置
    • 正确进行回溯操作
  3. 映射关系

    • 建立数字到字母的映射表
    • 处理7和9的4个字母情况
    • 使用数组或map存储映射
  4. 边界处理

    • 空字符串返回空数组
    • 单个数字直接返回对应字母
    • 确保所有情况都有正确输出
  5. 性能优化

    • 使用strings.Builder减少字符串拼接开销
    • 避免不必要的内存分配
    • 选择合适的递归深度

这个问题的关键在于理解回溯算法的核心思想掌握递归状态管理,通过深度优先搜索枚举所有可能的字母组合。特别是回溯操作,需要在每次递归调用后正确恢复状态,确保能够尝试所有可能的组合。时间复杂度为O(4^n),其中n是digits的长度,因为每个数字最多对应4个字母。

完整题解代码

package mainimport ("fmt""strings"
)// letterCombinations 电话号码的字母组合 - 回溯法
// 时间复杂度: O(4^n),其中n是digits的长度,每个数字最多对应4个字母
// 空间复杂度: O(n),递归调用栈深度
func letterCombinations(digits string) []string {if len(digits) == 0 {return []string{}}// 数字到字母的映射digitMap := map[byte]string{'2': "abc",'3': "def",'4': "ghi",'5': "jkl",'6': "mno",'7': "pqrs",'8': "tuv",'9': "wxyz",}var result []stringvar backtrack func(index int, current string)backtrack = func(index int, current string) {// 如果已经处理完所有数字,添加当前组合到结果if index == len(digits) {result = append(result, current)return}// 获取当前数字对应的字母letters := digitMap[digits[index]]// 尝试每个字母for _, letter := range letters {backtrack(index+1, current+string(letter))}}backtrack(0, "")return result
}// letterCombinationsIterative 迭代法 - 使用队列
// 时间复杂度: O(4^n)
// 空间复杂度: O(4^n),存储所有可能的组合
func letterCombinationsIterative(digits string) []string {if len(digits) == 0 {return []string{}}// 数字到字母的映射digitMap := map[byte]string{'2': "abc",'3': "def",'4': "ghi",'5': "jkl",'6': "mno",'7': "pqrs",'8': "tuv",'9': "wxyz",}// 使用队列存储当前所有可能的组合queue := []string{""}// 逐个处理每个数字for i := 0; i < len(digits); i++ {letters := digitMap[digits[i]]levelSize := len(queue)// 处理当前层的所有组合for j := 0; j < levelSize; j++ {current := queue[0]queue = queue[1:]// 为当前组合添加每个可能的字母for _, letter := range letters {queue = append(queue, current+string(letter))}}}return queue
}// letterCombinationsOptimized 优化版本 - 使用strings.Builder
// 时间复杂度: O(4^n)
// 空间复杂度: O(n)
func letterCombinationsOptimized(digits string) []string {if len(digits) == 0 {return []string{}}// 使用数组映射,避免map查找开销digitMap := [][]byte{{}, {}, // 0, 1 不对应字母{'a', 'b', 'c'},      // 2{'d', 'e', 'f'},      // 3{'g', 'h', 'i'},      // 4{'j', 'k', 'l'},      // 5{'m', 'n', 'o'},      // 6{'p', 'q', 'r', 's'}, // 7{'t', 'u', 'v'},      // 8{'w', 'x', 'y', 'z'}, // 9}var result []stringvar backtrack func(index int, current *strings.Builder)backtrack = func(index int, current *strings.Builder) {if index == len(digits) {result = append(result, current.String())return}digit := digits[index] - '0'letters := digitMap[digit]for _, letter := range letters {current.WriteByte(letter)backtrack(index+1, current)// 回溯:移除最后添加的字符currentStr := current.String()current.Reset()current.WriteString(currentStr[:len(currentStr)-1])}}backtrack(0, &strings.Builder{})return result
}// letterCombinationsBFS BFS方法 - 层序遍历
// 时间复杂度: O(4^n)
// 空间复杂度: O(4^n)
func letterCombinationsBFS(digits string) []string {if len(digits) == 0 {return []string{}}digitMap := map[byte]string{'2': "abc",'3': "def",'4': "ghi",'5': "jkl",'6': "mno",'7': "pqrs",'8': "tuv",'9': "wxyz",}// BFS队列queue := []string{""}for i := 0; i < len(digits); i++ {letters := digitMap[digits[i]]levelSize := len(queue)// 处理当前层的所有组合for j := 0; j < levelSize; j++ {current := queue[0]queue = queue[1:]// 为当前组合添加每个可能的字母for _, letter := range letters {queue = append(queue, current+string(letter))}}}return queue
}// letterCombinationsRecursive 纯递归方法 - 分治思想
// 时间复杂度: O(4^n)
// 空间复杂度: O(n)
func letterCombinationsRecursive(digits string) []string {if len(digits) == 0 {return []string{}}if len(digits) == 1 {return getLetters(digits[0])}// 分治:处理第一个数字,递归处理剩余数字firstLetters := getLetters(digits[0])remainingCombinations := letterCombinationsRecursive(digits[1:])var result []stringfor _, letter := range firstLetters {for _, combination := range remainingCombinations {result = append(result, string(letter)+combination)}}return result
}// getLetters 获取单个数字对应的字母
func getLetters(digit byte) []string {switch digit {case '2':return []string{"a", "b", "c"}case '3':return []string{"d", "e", "f"}case '4':return []string{"g", "h", "i"}case '5':return []string{"j", "k", "l"}case '6':return []string{"m", "n", "o"}case '7':return []string{"p", "q", "r", "s"}case '8':return []string{"t", "u", "v"}case '9':return []string{"w", "x", "y", "z"}default:return []string{}}
}func main() {// 测试用例1digits1 := "23"result1 := letterCombinations(digits1)fmt.Printf("示例1: digits = \"%s\"\n", digits1)fmt.Printf("输出: %v\n", result1)fmt.Printf("期望: [ad ae af bd be bf cd ce cf]\n")fmt.Printf("结果正确: %t\n", len(result1) == 9)fmt.Println()// 测试用例2digits2 := ""result2 := letterCombinations(digits2)fmt.Printf("示例2: digits = \"%s\"\n", digits2)fmt.Printf("输出: %v\n", result2)fmt.Printf("期望: []\n")fmt.Printf("结果正确: %t\n", len(result2) == 0)fmt.Println()// 测试用例3digits3 := "2"result3 := letterCombinations(digits3)fmt.Printf("示例3: digits = \"%s\"\n", digits3)fmt.Printf("输出: %v\n", result3)fmt.Printf("期望: [a b c]\n")fmt.Printf("结果正确: %t\n", len(result3) == 3)fmt.Println()// 额外测试用例digits4 := "234"result4 := letterCombinations(digits4)fmt.Printf("额外测试: digits = \"%s\"\n", digits4)fmt.Printf("输出数量: %d\n", len(result4))fmt.Printf("期望数量: 27 (3×3×3)\n")fmt.Printf("结果正确: %t\n", len(result4) == 27)fmt.Println()// 测试迭代版本fmt.Println("=== 迭代版本测试 ===")result1Iter := letterCombinationsIterative(digits1)result2Iter := letterCombinationsIterative(digits2)fmt.Printf("迭代版本示例1: %v\n", result1Iter)fmt.Printf("迭代版本示例2: %v\n", result2Iter)fmt.Printf("结果一致: %t\n", len(result1Iter) == len(result1) && len(result2Iter) == len(result2))fmt.Println()// 测试优化版本fmt.Println("=== 优化版本测试 ===")result1Opt := letterCombinationsOptimized(digits1)result2Opt := letterCombinationsOptimized(digits2)fmt.Printf("优化版本示例1: %v\n", result1Opt)fmt.Printf("优化版本示例2: %v\n", result2Opt)fmt.Printf("结果一致: %t\n", len(result1Opt) == len(result1) && len(result2Opt) == len(result2))fmt.Println()// 测试BFS版本fmt.Println("=== BFS版本测试 ===")result1BFS := letterCombinationsBFS(digits1)result2BFS := letterCombinationsBFS(digits2)fmt.Printf("BFS版本示例1: %v\n", result1BFS)fmt.Printf("BFS版本示例2: %v\n", result2BFS)fmt.Printf("结果一致: %t\n", len(result1BFS) == len(result1) && len(result2BFS) == len(result2))fmt.Println()// 测试递归版本fmt.Println("=== 递归版本测试 ===")result1Rec := letterCombinationsRecursive(digits1)result2Rec := letterCombinationsRecursive(digits2)fmt.Printf("递归版本示例1: %v\n", result1Rec)fmt.Printf("递归版本示例2: %v\n", result2Rec)fmt.Printf("结果一致: %t\n", len(result1Rec) == len(result1) && len(result2Rec) == len(result2))fmt.Println()// 边界值测试fmt.Println("=== 边界值测试 ===")boundaryTests := []string{"",     // 空字符串"2",    // 单个数字"99",   // 两个相同数字"2345", // 四个不同数字"7777", // 四个相同数字}for _, test := range boundaryTests {result := letterCombinations(test)expectedCount := 1for _, digit := range test {switch digit {case '7', '9':expectedCount *= 4default:expectedCount *= 3}}fmt.Printf("digits = \"%s\", result count = %d, expected = %d\n", test, len(result), expectedCount)}
}
http://www.xdnf.cn/news/1329211.html

相关文章:

  • idea中如何设置文件的编码格式
  • 【撸靶笔记】第七关:GET - Dump into outfile - String
  • Python爬虫实战:研究ICP-Checker,构建ICP 备案信息自动查询系统
  • 【MySQL】--- 库表操作
  • 字节开源了一款具备长期记忆能力的多模态智能体:M3-Agent
  • 【数据结构】堆和二叉树详解(下)
  • 构建自主企业:AgenticOps 的技术蓝图
  • 学习嵌入式的第二十一天——数据结构——链表
  • 可以一键生成PPT的AI PPT工具(最新整理)
  • 从机器视觉到图像识别:计算机视觉的多维探索
  • 图论\dp 两题
  • Matplotlib数据可视化实战:Matplotlib基础与实践-快速上手数据可视化
  • 数据结构-栈和队列
  • kubeadm部署k8s集群环境搭建
  • consul-基础概念
  • 信号以及共享内存
  • strlen 函数的使用与模拟实现
  • 算法——质数筛法
  • 106、【OS】【Nuttx】【周边】文档构建渲染:安装 Sphinx 扩展(下)
  • yolov5s.onnx转rk模型以及相关使用详细教程
  • 玳瑁的嵌入式日记D20-08019(数据结构)
  • 安装DDNS-go
  • Linux操作系统编程——进程间的通信
  • RocketMq消费者动态订阅topic
  • RK3568 Linux驱动学习——Linux设备树
  • Linux下Mysql命令,创建mysql,删除mysql
  • Win/Linux笔记本合盖不睡眠设置指南
  • 小程序插件使用
  • RWA加密金融高峰论坛星链品牌全球发布 —— 稳定币与Web3的香港新篇章
  • Vue 2 项目中快速集成 Jest 单元测试(超详细教程)