当前位置: 首页 > 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.dtcms.com/a/338460.html

相关文章:

  • SpreadJS 协同服务器 MongoDB 数据库适配支持
  • [系统架构设计师]云原生架构设计理论与实践(十四)
  • flink+clinkhouse安装部署
  • 【数据结构】二叉树-堆(深入学习 )
  • Flink原理与实践 · 第三章总结
  • Jenkins项目发布基础
  • 代码随想录算法训练营四十六天|图论part04
  • CSS封装大屏自定义组件(标签线)
  • 在python中等号左边的都是对象,在matlab中等号a = 3+2 a就是个变量
  • 关系型数据库与非关系型数据库
  • 用户认证技术
  • 【笔记】扩散模型(一一):Stable Diffusion XL 理论与实现
  • 力扣hot100:盛最多水的容器:双指针法高效求解最大容量问题(11)
  • [NSSCTF 2022 Spring Recruit]rrrsssaaa
  • 94、23种设计模式之工厂方法模式(3/23)
  • 用户认证技术和应用控制技术总结
  • 接口和抽象类的区别(面试回答)
  • leetcode43. 字符串相乘
  • 遗传算法求解冷链路径优化问题matlab代码
  • [ Spring 框架 ] 框架搭建和属性赋值
  • android 实现表格效果
  • 《彩色终端》诗解——ANSI 艺术解(DeepSeek)
  • shell脚本第一阶段
  • Image-to-Music API 接入文档(图片生成音乐)
  • 【新手易混】find 命令中 -perm 选项的知识点
  • ANSI终端色彩控制知识散播(I):语法封装(Python)——《彩色终端》诗评
  • JavaScript 性能优化实战技术指南
  • Coze AI大模型 Docker 部署流程详解
  • 设计模式(四)——责任链模式
  • Spring 三级缓存:破解循环依赖的底层密码