电话号码字母组合是算法学习中的经典回溯问题,源自LeetCode第17题。这个问题模拟了老式手机键盘的数字与字母映射关系——每个数字键(2-9)对应若干字母(如2对应abc),要求给定一串数字后,输出所有可能的字母组合。
实际开发中,这类问题常见于:
以输入"23"为例,2对应"abc",3对应"def",则需要输出["ad","ae","af","bd","be","bf","cd","ce","cf"]这9种组合。关键在于如何高效遍历所有可能性而不遗漏。
回溯本质是DFS的变体,通过递归尝试所有可能的选择路径,当发现当前路径无法满足条件时回退到上一步。其核心框架为:
对于本题:
python复制def letterCombinations(digits):
if not digits:
return []
phone = {
'2': 'abc',
'3': 'def',
'4': 'ghi',
'5': 'jkl',
'6': 'mno',
'7': 'pqrs',
'8': 'tuv',
'9': 'wxyz'
}
res = []
def backtrack(index, path):
if index == len(digits):
res.append(''.join(path))
return
current_digit = digits[index]
for letter in phone[current_digit]:
path.append(letter)
backtrack(index + 1, path)
path.pop()
backtrack(0, [])
return res
关键点说明:
index记录当前处理到的数字位置path保存当前组合路径假设输入数字串长度为n,最坏情况下每个数字对应4个字母(7/9键),时间复杂度为O(4^n)。空间复杂度主要来自递归调用栈,同样为O(4^n)。
回溯虽直观但存在栈溢出风险。迭代解法利用队列实现BFS:
python复制from collections import deque
def letterCombinations(digits):
if not digits:
return []
phone = {
'2': 'abc',
'3': 'def',
'4': 'ghi',
'5': 'jkl',
'6': 'mno',
'7': 'pqrs',
'8': 'tuv',
'9': 'wxyz'
}
queue = deque([''])
for digit in digits:
level_size = len(queue)
for _ in range(level_size):
curr = queue.popleft()
for letter in phone[digit]:
queue.append(curr + letter)
return list(queue)
优势:
| 输入情况 | 处理方式 | 示例输出 |
|---|---|---|
| 空字符串 | 直接返回空列表 | [] |
| 包含1或0 | 可过滤或抛出异常 | 忽略 |
| 超长输入(>10) | 添加长度检查或分块处理 | 警告提示 |
当处理长数字串时(如15位以上):
改进版生成器实现:
python复制def letterCombinations_gen(digits):
phone = {...} # 同上
def backtrack(index, path):
if index == len(digits):
yield ''.join(path)
return
# 其余逻辑相同
return list(backtrack(0, [])) if digits else []
结合词典可大幅减少无效组合。例如实现手机T9输入法时:
python复制valid_words = set(['apple', 'cat', 'dog', ...])
def filtered_combinations(digits):
for combo in letterCombinations_gen(digits):
if combo in valid_words:
yield combo
不同国家的手机键盘映射不同,需扩展phone字典:
python复制phone_fr = {
'2': 'abcé',
'3': 'defè',
# ...法语特殊字符
}
添加递归树打印帮助理解:
python复制def backtrack(index, path, depth=0):
print(' '*depth + f'Level {index}: {path}')
# ...其余逻辑
输出示例:
code复制Level 0: []
Level 1: ['a']
Level 2: ['a','d']
Level 1: ['b']
Level 2: ['b','d']
现象:长输入导致栈溢出
解决:
现象:相同数字段被重复处理
优化:添加memoization缓存中间结果
测试用例:"23456789"(8位):
要求生成3-5字母的组合:
python复制def limited_combinations(digits, min_len=3, max_len=5):
def backtrack(index, path):
if len(path) == max_len or index == len(digits):
if min_len <= len(path) <= max_len:
yield ''.join(path)
return
# 其余逻辑相同
某些字母出现概率更高:
python复制from random import choices
weighted_map = {
'2': [('a',5), ('b',3), ('c',2)] # a出现概率50%
}
def weighted_backtrack(index, path):
if index == len(digits):
return [''.join(path)]
res = []
letters, weights = zip(*weighted_map[digits[index]])
for letter in choices(letters, weights, k=1):
path.append(letter)
res += weighted_backtrack(index+1, path)
path.pop()
return res
处理超长输入时流式输出:
python复制def streaming_combinations(digits, chunk_size=1000):
buffer = []
for combo in letterCombinations_gen(digits):
buffer.append(combo)
if len(buffer) >= chunk_size:
yield buffer
buffer = []
if buffer:
yield buffer
我在实际项目中发现,当组合数超过百万时,使用生成器配合文件存储是最可靠的方式。曾处理过一个12位数字的案例(约280万组合),直接列表存储会导致内存溢出,而分块写入文件后处理则稳定运行。