【题目描述】
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例 1:
输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
示例 2:
输入:digits = ""
输出:[]
示例 3:
输入:digits = "2"
输出:["a","b","c"]
【题目链接】. - 力扣(LeetCode)
【解题代码】
package string;
import java.util.ArrayList;
import java.util.List;
public class LetterCombinations {
public static void main(String[] args) {
String digits = "23";
System.out.println(new LetterCombinations().letterCombinations(digits));
}
public List<String> letterCombinations(String digits) {
// 特殊情况处理,直接返回空数组
if (digits.length() == 0 || digits.length() > 40) return new ArrayList<>();
// 新建一个动态数组,存储结果
List<String> charList = new ArrayList<>();
// 新建一个StringBuilder,存储字符串生成的中间结果
StringBuilder sb = new StringBuilder();
appendChars(digits, 0, sb, charList);
return charList;
}
private void appendChars(String digits, int n, StringBuilder sb, List<String> charList) {
// 已经处理到字符串末尾, 存储生成的字母组合结果
if (n == digits.length()) {
charList.add(sb.toString());
} else {
// 获取当前数字
char c1 = digits.charAt(n);
// 根据字符类型,确定其可映射的字母个数,除了'7'和'9'是4个,其余都是3个
int len = (c1 == '7' || c1 == '9') ? 4 : 3;
// '7'数字可映射的字母长度为4,之前所有数字和'8'可映射的字母长度都是3,最后一个忽略不计,因此'7'之前的字母长度为3N,'7'
// 之后的字母长度为3N+1
int delta = c1 > '7' ? 1 : 0;
// 依次处理当前数字可映射的字母
for (int i = 0; i < len; i++) {
// 计算当前可映射的字母数值
char c2 = (char) ('a' + (c1 - '2') * 3 + delta + i);
// 添加到StringBuilder
sb.append(c2);
// 处理下一个数字
appendChars(digits, n + 1, sb, charList);
// 回溯删除刚才添加的字母
sb.deleteCharAt(sb.length() - 1);
}
}
}
}
【解题思路】
拿到题目内容,仔细分析一下,得出以下几点思路:
- 需要采用回溯的方式来处理所有字母组合,即对对输入字符串中所有数字依次添加可能映射的字母,递归调用处理下一个数字后,删除该字母,然后处理下一个字母;
- 递归处理的终止条件就是已经处理到字符串末尾
- 需要计算数字数值和所有映射的字母之间的数值关系,分析可得,数字'7'可映射的字母长度为4,之前所有数字和'8'可映射的字母长度都是3,最后一个忽略不计,因此'7'之前的字母长度为3N,'7'之后的字母长度为3N+1。N为当前数字和起始数字‘2’之差
根据上面思路,很快写出解题代码,并提交LeetCode成功,下面看看整个算法实现的解题步骤
【解题步骤】
- 初始化变量,新建一个动态数组存储字母组合结果,以及个StringBuilder,存储字符串生成的中间结果
List<String> charList = new ArrayList<>(); StringBuilder sb = new StringBuilder();
- 调用字母组合生成的递归回溯函数,从第一个数字开始
appendChars(digits, 0, sb, charList);
- 递归回溯函数里,如果已经处理到字符串末尾, 递归结束,存储生成的字母组合结果
if (n == digits.length()) { charList.add(sb.toString()); }
- 否则获取当前数字,并初始化该数字映射到字母的公式变量
// 获取当前数字 char c1 = digits.charAt(n); // 根据字符类型,确定其可映射的字母个数,除了'7'和'9'是4个,其余都是3个 int len = (c1 == '7' || c1 == '9') ? 4 : 3; // '7'数字可映射的字母长度为4,之前所有数字和'8'可映射的字母长度都是3,最后一个忽略不计,因此'7'之前的字母长度为3N,'7' // 之后的字母长度为3N+1 int delta = c1 > '7' ? 1 : 0;
- 依次处理当前数字可映射的字母:第一步添加计算当前可映射的字母数值,添加到中间变量StringBuilder中,第二步递归处理下一个数字,第三步回溯删除刚才添加的字母
// 依次处理当前数字可映射的字母 for (int i = 0; i < len; i++) { // 计算当前可映射的字母数值 char c2 = (char) ('a' + (c1 - '2') * 3 + delta + i); // 添加到StringBuilder sb.append(c2); // 处理下一个数字 appendChars(digits, n + 1, sb, charList); // 回溯删除刚才添加的字母 sb.deleteCharAt(sb.length() - 1); }
- 最后所有递归处理完毕,返回处理生成的字母组合结果
return charList;
【思考总结】
- 要掌握回溯算法的思想和实现,此类问题就能迎刃而解:“回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。这种走不通就退回再走的技术为回溯法,所以也经常称作试探法对于此类问题”
- 这一题另外一个关键需要计算数字数值和所有映射的字母之间的数值关系,我自己发现的公式:
这个比官方题解的hashmap死板存储方式,应该还是要高明一些:)char c2 = (char) ('a' + (c1 - '2') * 3 + delta + i);
- LeetCode解题之前,一定不要看题解,看了就“破功”了!