【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.pswp.cn/diannao/96400.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/96400.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

全文 part1 - DGEMM Using Tensor Cores, and Its Accurate and Reproducible Versions

摘要 本文提出了一种在 NVIDIA 图形处理器&#xff08;GPU&#xff09;的张量核心&#xff08;Tensor Cores&#xff0c;仅含 FP16、INT8 等 GEMM 计算功能&#xff09;上实现 FP64&#xff08;双精度&#xff0c;DGEMM&#xff09;和 FP32&#xff08;单精度&#xff0c;SGEMM…

Hexo 博客图片托管:告别本地存储,用 PicGo + GitHub 打造高速稳定图床

之前刚开始进行Hexo博客撰写&#xff0c;图片都保存在本地Hexo源文件目录&#xff08;source/images/&#xff09;文件夹&#xff0c;随着图片增多&#xff0c;管理起来压力增大&#xff0c;于是产生了使用图床&#xff0c;引入外链进行图片存储的想法 Pros and Cons 提升部署…

关于 VScode 无法连接 Linux 主机并报错 <未能下载 VScode 服务器> 的解决方案

1. 出现的情况 VScode 远程登录 Linux 主机, 出现一下报错:2. 检查方案 2.1 VScode 方面 菜单栏: 点击 <帮助> →\to→ 点击 <关于> 在出现的弹窗中记录 [提交: ] 之后的字符串 (暂且将该字符串命名为变量 $commit_id) 2.2 Linux 方面 使用 ssh or MobaXterm 远程登…

泛型与反射

也是重新温习了下泛型与反射,反射基本就是一些api理解即可,不过需要注意类加载器原理,而泛型则需要理解其设计思想,可以代替Object,更加灵活,可读性强。泛型泛型如果指定后,编译阶段就会检查,不让乱输其他类型,必须是引用类型; 如果不指定就默认Object// 如果指定泛型, 就必须存…

Docker端口映射与数据卷完全指南

目录 Docker端口映射与数据卷完全指南 1. 端口映射:连接Docker容器与外部世界 1.1 为什么需要端口映射 1.2 实现端口映射 1.3 查看端口映射 1.4 修改端口映射(高级操作) 2. 数据卷:Docker数据持久化解决方案 2.1 数据持久化问题 2.2 数据卷的含义 2.3 数据卷的特点 2.4 挂载…

【Linux篇章】穿越网络迷雾:揭开 HTTP 应用层协议的终极奥秘!从请求响应到实战编程,从静态网页到动态交互,一文带你全面吃透并征服 HTTP 协议,打造属于你的 Web 通信利刃!

本篇摘要 本篇将介绍何为HTTP协议&#xff0c;以及它的请求与答复信息的格式&#xff08;请求行&#xff0c;请求包头&#xff0c;正文等&#xff09;&#xff0c;对一些比较重要的部分来展开讲解&#xff0c;其他不常用的即一概而过&#xff0c;从静态网页到动态网页的过渡&a…

QT的项目pro qmake编译

使用qmake管理Qt库的子工程示例-CSDN博客 top_srcdir top_builddir

语音交互系统意图识别介绍和构建

一、意图识别简介**意图识别&#xff08;Intent Recognition&#xff09;**是语音交互系统的核心组件&#xff0c;用于理解用户语音输入背后的真实目的&#xff08;如查询天气、播放音乐等&#xff09;。输入&#xff1a;语音转文本&#xff08;ASR输出&#xff09;的语句输出&…

DINOv3 重磅发布

2025年8月14日 Meta 发布了 DINOv3 。 主页&#xff1a;https://ai.meta.com/dinov3/ 论文&#xff1a;DINOv3 HuggingFace地址&#xff1a;https://huggingface.co/collections/facebook/dinov3-68924841bd6b561778e31009 官方博客&#xff1a;https://ai.meta.com/blog/d…

ansible playbook 实战案例roles | 实现基于firewalld添加端口

文章目录一、核心功能描述二、roles内容2.1 文件结构2.2 主配置文件2.3 tasks文件内容免费个人运维知识库&#xff0c;欢迎您的订阅&#xff1a;literator_ray.flowus.cn 一、核心功能描述 这个 Ansible Role (firewalld) 的核心功能是&#xff1a;动态地、安全地配置 firewal…

【深度学习实战(55)】记录一次在新服务器上使用docker的流程

使用docker&#xff1a;apt-get install dockersudo usermod -aG docker sliu &#xff08;将用户 sliu 添加到 docker 用户组&#xff09;newgrp docker &#xff08;刷新&#xff09;docker imagessudo docker load --input /home/sliu/workspace/env/shuai_docker.tar &…

面试后的跟进策略:如何提高录用几率并留下专业印象

面试结束后&#xff0c;许多求职者认为自己的任务已经完成&#xff0c;只需等待结果通知。然而&#xff0c;面试后的跟进策略同样是求职过程中的关键环节&#xff0c;它不仅能提高你的录用几率&#xff0c;还能展示你的专业素养和持续兴趣。本文将结合酷酷面试平台的专业建议&a…

深入解析RAGFlow六阶段架构

下面用“流程图 六阶段拆解”的方式&#xff0c;把 RAGFlow 的完整流程逐层剖开&#xff0c;力求把每一步的输入、输出、可选策略、内部机制都讲清楚。 ──────────────────────── 一、总览图&#xff08;先建立体感&#xff09; 用户提问 │ ├─→【…

Go语言中的迭代器模式与安全访问实践

Go语言中的迭代器模式与安全访问实践 1. 迭代器模式在Go中的演进 1.1 传统迭代器模式回顾 在传统面向对象语言中&#xff0c;迭代器模式通常涉及三个核心组件&#xff1a;可迭代集合接口(Iterable)迭代器接口(Iterator)具体实现类// 传统迭代器模式示例 type Iterator interfac…

从零开始:JDK 在 Windows、macOS 和 Linux 上的下载、安装与环境变量配置

前言 在进入 Java 世界之前&#xff0c;搭建一个稳定、可用的开发环境是每个开发者必须迈过的第一道门槛。JDK&#xff08;Java Development Kit&#xff09;作为 Java 程序开发的核心工具包&#xff0c;其正确安装与环境变量配置直接关系到后续编译、运行、调试等所有开发流程…

【音视频】芯片、方案、市场信息收集

系统级芯片安霸&#xff08;Ambarella&#xff09;Ambarella H22/H32&#xff1a;高端方案&#xff0c;支持8K/4K高帧率录制&#xff0c;低功耗&#xff0c;广泛用于GoPro Hero 11/12、Insta360等旗舰机型。 Ambarella A12/A10&#xff1a;早期主流方案&#xff0c;支持4K60fps…

中科米堆CASAIM提供机加工件来料自动化测量尺寸方案

机加工行业面临日益严格的质量追溯要求&#xff0c;来料质量的稳定性直接影响着后续生产效率与成品合格率。传统人工检测方式受限于接触式工具的测量精度与操作效率&#xff0c;难以应对小批量、多品种的现代生产需求。传统机加工件来料检测长期面临这些问题&#xff1a;其一&a…

MySQL只操作同一条记录也会死锁吗?

大家好&#xff0c;我是锋哥。今天分享关于【MySQL只操作同一条记录也会死锁吗?】面试题。希望对大家有帮助&#xff1b; MySQL只操作同一条记录也会死锁吗? 超硬核AI学习资料&#xff0c;现在永久免费了&#xff01; 在 MySQL 中&#xff0c;死锁通常是由于多个事务对不同…

知识蒸馏 Knowledge Distillation 论文 Generalized Knowledge Distillation (GKD) 乘法法则、全概率公式、贝叶斯定理

知识蒸馏 Knowledge Distillation 论文 Generalized Knowledge Distillation (GKD) 乘法法则、全概率公式、贝叶斯定理 flyfish 代码实践 On-Policy Distillation of Language Models: Learning from Self-Generated Mistakes 设定&#xff08;方便算数&#xff09;&#x…

Fastjson 2.x踩坑——序列化Java字段为null值默认输出

先上无法实现效果的代码&#xff0c;我的目的是序列化时如果数字型字段为null则填0&#xff0c;尽可能保证数据整齐。 Data NoArgsConstructor AllArgsConstructor ToString JSONType(serializeFeatures {JSONWriter.Feature.WriteNulls,JSONWriter.Feature.WriteMapNullValue…