文章目录
- 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个字母,需要生成所有可能的排列组合。
算法分析
这道题的核心思想是回溯枚举,主要解法包括:
- 回溯法:使用递归和回溯生成所有组合(推荐)
- 迭代法:使用队列进行层序遍历
- 优化版本:使用strings.Builder提高字符串操作效率
- BFS方法:广度优先搜索生成组合
- 递归分治:使用分治思想逐步构建组合
问题本质分析
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[结果收集]
组合生成过程可视化
数字映射关系
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
各种解法对比
算法流程图
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个字母情况
- 使用数组或map存储映射
-
边界处理:
- 空字符串返回空数组
- 单个数字直接返回对应字母
- 确保所有情况都有正确输出
-
性能优化:
- 使用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)}
}