电话号码的字母组合
题目要求
给定一个字符串,该字符串由数字 2 到 9 组成。要求编写一个算法,找出这个字符串所能代表的所有可能的字母组合。字母组合与电话按键上的字母对应,即:
- 2 对应 a, b, c
- 3 对应 d, e, f
- 4 对应 g, h, i
- 5 对应 j, k, l
- 6 对应 m, n, o
- 7 对应 p, q, r, s
- 8 对应 t, u, v
- 9 对应 w, x, y, z
注意:
- 输入字符串的长度不固定,但仅包含数字 2-9。
- 输出的字母组合可以是任意顺序。
- 数字 1 不对应任何字母,因此在输入字符串中不会出现。
解题思路
这个问题可以通过回溯算法来解决。回溯算法是一种通过探索所有可能的候选解来找出所有解的算法。如果候选解被确认不是一个有效的解,回溯算法会丢弃它,并回溯到上一步然后尝试另一种可能。
对于这个问题,我们可以定义如下的回溯过程:
- 从输入字符串的第一个数字开始。
- 将当前数字对应到其能代表的所有字母。
- 对于当前数字对应的每一个字母,将其加到目前为止的组合中,并对输入字符串的下一个数字进行相同的处理。
- 当我们到达输入字符串的末尾时,我们将当前的字母组合加入到结果集中。
- 如果还没有到达字符串末尾,继续对剩余的数字进行步骤 2 到 4 的处理。
- 一旦处理完一个字母,回溯到前一个字母,移除当前字母,尝试下一个可能的字母组合。
通过这样的方式,我们可以确保探索了所有可能的字母组合,并且当我们到达字符串的末尾时,我们会有一个完整的组合加入到结果集中。这个过程会一直重复,直到所有的组合都被找到为止。
Golang 版本
package main
import "fmt"
func letterCombinations(digits string) []string {
if len(digits) == 0 {
return []string{}
}
phoneMap := map[byte]string{
'2': "abc", '3': "def", '4': "ghi", '5': "jkl",
'6': "mno", '7': "pqrs", '8': "tuv", '9': "wxyz",
}
var combinations []string
var backtrack func(index int, path []byte)
backtrack = func(index int, path []byte) {
if index == len(digits) {
combinations = append(combinations, string(path))
return
}
letters := phoneMap[digits[index]]
for i := 0; i < len(letters); i++ {
path = append(path, letters[i])
backtrack(index+1, path)
path = path[:len(path)-1]
}
}
backtrack(0, []byte{})
return combinations
}
func main() {
digits := "23"
fmt.Println(letterCombinations(digits))
}
这段代码定义了一个letterCombinations
函数,它接受一个字符串digits
作为输入,并返回一个字符串切片,包含了所有可能的字母组合。在main
函数中,我们调用了letterCombinations
函数,并打印出结果。这个实现使用了回溯算法,通过递归函数backtrack
来生成所有可能的字母组合。
C++版本
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
using namespace std;
class Solution {
public:
vector<string> letterCombinations(string digits) {
if (digits.empty()) return {};
unordered_map<char, string> phoneMap{
{'2', "abc"}, {'3', "def"}, {'4', "ghi"}, {'5', "jkl"},
{'6', "mno"}, {'7', "pqrs"}, {'8', "tuv"}, {'9', "wxyz"}
};
vector<string> combinations;
string currentCombination;
backtrack(combinations, phoneMap, digits, 0, currentCombination);
return combinations;
}
private:
void backtrack(vector<string>& combinations, const unordered_map<char, string>& phoneMap,
const string& digits, int index, string& currentCombination) {
if (index == digits.length()) {
combinations.push_back(currentCombination);
return;
}
const string& letters = phoneMap.at(digits[index]);
for (const char& letter : letters) {
currentCombination.push_back(letter);
backtrack(combinations, phoneMap, digits, index + 1, currentCombination);
currentCombination.pop_back();
}
}
};
int main() {
Solution solution;
string digits = "23";
vector<string> combinations = solution.letterCombinations(digits);
for (const string& combination : combinations) {
cout << combination << endl;
}
return 0;
}
这段 C++代码实现了一个Solution
类,其中包含了letterCombinations
方法,用于计算并返回所有可能的字母组合。main
函数中创建了一个Solution
对象,并调用了letterCombinations
方法,然后打印出所有的组合。这个实现同样使用了回溯算法,通过私有方法backtrack
来递归地构建所有可能的组合。
Python 版本
from typing import List
class Solution:
def letterCombinations(self, digits: str) -> List[str]:
if not digits:
return []
phone_map = {
'2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl',
'6': 'mno', '7': 'pqrs', '8': 'tuv', '9': 'wxyz'
}
def backtrack(index: int, path: str):
if index == len(digits):
combinations.append(path)
return
for letter in phone_map[digits[index]]:
backtrack(index + 1, path + letter)
combinations = []
backtrack(0, "")
return combinations
# Example usage:
if __name__ == '__main__':
solution = Solution()
digits = "23"
print(solution.letterCombinations(digits))
这段 Python 代码定义了一个Solution
类,其中包含了letterCombinations
方法,它接受一个字符串digits
作为输入,并返回一个字符串列表,包含了所有可能的字母组合。在if __name__ == '__main__':
块中,我们创建了一个Solution
实例,并调用了letterCombinations
方法,然后打印出结果。这个实现使用了回溯算法,通过嵌套的backtrack
函数来递归地生成所有可能的字母组合。
Java 版本
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
public class Solution {
private Map<Character, String> phoneMap = new HashMap<Character, String>() {{
put('2', "abc");
put('3', "def");
put('4', "ghi");
put('5', "jkl");
put('6', "mno");
put('7', "pqrs");
put('8', "tuv");
put('9', "wxyz");
}};
public List<String> letterCombinations(String digits) {
List<String> combinations = new ArrayList<>();
if (digits == null || digits.length() == 0) {
return combinations;
}
backtrack(combinations, digits, 0, new StringBuilder());
return combinations;
}
private void backtrack(List<String> combinations, String digits, int index, StringBuilder path) {
if (index == digits.length()) {
combinations.add(path.toString());
return;
}
String possibleLetters = phoneMap.get(digits.charAt(index));
for (char letter : possibleLetters.toCharArray()) {
path.append(letter);
backtrack(combinations, digits, index + 1, path);
path.deleteCharAt(path.length() - 1);
}
}
public static void main(String[] args) {
Solution solution = new Solution();
String digits = "23";
List<String> combinations = solution.letterCombinations(digits);
for (String combination : combinations) {
System.out.println(combination);
}
}
}
这段 Java 代码定义了一个Solution
类,其中包含了letterCombinations
方法,它接受一个字符串digits
作为输入,并返回一个字符串列表,包含了所有可能的字母组合。main
方法中创建了一个Solution
实例,并调用了letterCombinations
方法,然后打印出所有的组合。这个实现使用了回溯算法,通过私有方法backtrack
来递归地构建所有可能的组合。
Rust 版本
use std::collections::HashMap; fn letter_combinations(digits: String) -> Vec<String> { let mut res = Vec::new(); if digits.is_empty() { return res; } let phone_map: HashMap<char, &str> = [ ('2', "abc"), ('3', "def"), ('4', "ghi"), ('5', "jkl"), ('6', "mno"), ('7', "pqrs"), ('8', "tuv"), ('9', "wxyz"), ].iter().cloned().collect(); fn backtrack( res: &mut Vec<String>, phone_map: &HashMap<char, &str>, digits: &str, index: usize, combination: &mut String, ) { if index == digits.len() { res.push(combination.clone()); return; } if let Some(letters) = phone_map.get(&digits.chars().nth(index).unwrap()) { for letter in letters.chars() { combination.push(letter); backtrack(res, phone_map, digits, index + 1, combination); combination.pop(); } } } backtrack(&mut res, &phone_map, &digits, 0, &mut String::new()); res } fn main() { let digits = "23".to_string(); let combinations = letter_combinations(digits); for combination in combinations { println!("{}", combination); } }
这段 Rust 代码定义了一个letter_combinations
函数,它接受一个String
类型的digits
作为输入,并返回一个包含所有可能字母组合的Vec<String>
。在main
函数中,我们调用了letter_combinations
函数,并遍历打印出所有的组合。这个实现使用了回溯算法,通过一个嵌套的backtrack
函数来递归地生成所有可能的字母组合。
总结
上述解法在不同编程语言中实现了相同的功能:给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。这个问题通常通过回溯算法来解决,因为需要探索所有可能的字母组合,直到找到所有的解。
在每种语言的实现中,都定义了一个映射(字典、哈希表等),将每个数字映射到对应的字母字符串。然后,使用回溯的方法,从第一个数字开始,对于每个可能的字母,都递归地进行探索,直到达到字符串的末尾。每当到达字符串的末尾时,就将当前的字母组合添加到结果集中。
不同语言之间的主要区别在于语法和一些特定的 API 调用。例如:
- 在 C++中,使用了
std::vector
和std::unordered_map
来存储组合和映射。 - 在 Python 中,使用了列表和字典,并且有更简洁的循环和字符串处理方式。
- 在 Java 中,使用了
ArrayList
和HashMap
,以及StringBuilder
来高效地构建字符串。 - 在 Rust 中,使用了
Vec
和HashMap
,以及 Rust 特有的所有权和借用规则来管理内存。
尽管实现的细节不同,但所有这些解法都遵循了相同的算法逻辑。