题目描述:
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例:
输入:”23”
输出:[“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”].
思路一:回溯法
每一个数字映射多个字母,每个字母要和剩余数字的每个字母一一进行组合,可以当成一个树形问题处理,拿 23 的组合来举例,图形如下:
问题就转化成了从根节点到空节点有多少条路径,也就是一个组合问题,选择回溯解决。
回溯需要做的三件事:
- 确定回溯递归函数的参数
- 确定终止条件
- 确定函数内、遍历的通用逻辑
递归函数参数
第一个参数,为数字字符串;
第二个参数,为下标,用来获取进行到的数字,会在递归调用时不断向后移;
第三个参数,为已组合的字母字符串,在函数内遍历时使用,继续组合遍历到的字母。
通用逻辑
通过传入的数字,得到第一个数字对应的字母集合,遍历该集合,继续递归调用函数,得到第二个数字对应的字母集合,依次类推。
每次递归都传入该次组合的字母字符串,以便继续递归可以叠加。待递归调用到条件结束,则回溯到上一层,继续下一个字母递归调用组合。
终止条件
结束条件为递归到最底层,也就是字母都取完为止,可通过移动的下标,判断和字符串的长度是否相同,来决定终止。
代码:
先定义一个哈希映射关系,存入每个数字对应的字母,跟上面的电话图一致,方便根据传入的数字找到对应的字母。
let phone = {
"0":[],
"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"]
}
var letterCombinations = function(digits) {
if(digits.length === 0) return []
const result = []
// digits 要查找的数字字符串;startIndex 进行到的数字下标;comString 已经组合的字符串
const loop = (digits, startIndex, comString) => {
if(startIndex === digits.length) {
result.push(comString)
return
}
const letter = digits[startIndex] // 拿到对应的数字
if(letter === '0') return
// 字符不符合规定,抛错
if(!/[1-9]/.test(letter)){
throw new Error('不符合数字的规定')
}
const letterArr = phone[letter] // 找到数字对应的字符集
// 循环数字对应的字符串集合
for(let i = 0; i< letterArr.length; i++){
// 继续递归,进行下一个数字
loop(digits, startIndex + 1, comString + letterArr[i])
}
}
loop(digits, 0, '')
return result
};
以上使用的是下标移动获取数字,也可以不断的截取字符串 digits.slice(1),终止条件判断为字符串为空了。
时间复杂度:暂无。
思路二:广度优先
**
回溯方法是选择一条路径直到无法选取为止,然后再返回寻找下一个路径,直到底层,而广度优先是以层为维度,不断组合好每一层的字符串,然后继续向下一层组合,也就是一层一层的组合。
画图示意:
该思路是维护一个数组队列,初始第一层是一个空的字符串;开始依次循环数字字符串,拿到数字对应的字母集合遍历,将上一层的字符串拼上该层的字母,新组合的字符入到队列,上一层的字符串出队。
最后遍历完整个数字后,队列中字符串,就是遍历到底层的组合。
代码:
/**
* @param {string} digits
* @return {string[]}
*/
let phone = {
"0":[],
"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"]
}
var letterCombinations = function(digits) {
if(digits.length === 0) return []
const result = []
result.push('') // 第一层
for(let i = 0; i < digits.length; i++){
let len = result.length; // 层级节点数
let strArr = phone[digits[i]] // 数字对应的字母
for(let j = 0; j < len; j++){
let val = result.shift() // 原来层级的节点出队
// 新的层级节点组合进入队列中
for(let k = 0; k < strArr.length; k++){
result.push(val + strArr[k])
}
}
}
return result
};
时间复杂度:暂无。
总结:
思路一是回溯法的应用,通过暴力的循环嵌套循环得到解,除此之外,类似这样的组合题,都基本使用这样的思路解决问题。