算法成长记:电话号码的字母组合
电话号码的字母组合给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。输入:digits = “23”输出:[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”]输入:digits = “”输出:[]输入:digits = “2”输出:[“a”,“b
电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
输入:digits = “23”
输出:[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”]
输入:digits = “”
输出:[]
输入:digits = “2”
输出:[“a”,“b”,“c”]
这个题目看似不陌生,但是实现起来还是需要转换一下思想。
首先,我们需要把数字转换成字符串,我们首先会想到字典匹配的方法;
之后我们得到的是一个字符串数组,字符串数组的长度为3或者为4;
那就是组合的问题了,我们知道组合的情况总数是字符串长度相乘;
那么我们怎么来实现组合呢?
思路一:循环,有几层循环呢?
先是对digits 循环,digits 的长度说明存在几个字符串;
然后是对字符串循环,digits[i]和str[j]匹配;
我们怎么去存数据呢?返回的结果应该是怎么样的?
我们可以这样,当取出第一个字符串的时候(假设为’abc’),它和下一个’def’匹配的时候,(双循环),把他们的结果push到一个数组,我们可以得到这个数组[‘ad’,‘ae’,‘af’,
‘bd’,‘be’,‘bf’,‘ce’,cd’,‘cf’];
我们循环再循环下一个,那么上面得到这个数组再和‘ghi’做双循环,这样就能循环全部;
最开始我们可以用一个’'来和第一个字符串做循环。
代码:
/**
* @param {string} digits
* @return {string[]}
*/
var letterCombinations = function(digits) {
if (!digits) { return []; }
const strMap = ["", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"];
let result = [""]; //初始化空数组
for (let num of digits) { //遍历输入的数字
let nextResult = [];// 用来保存结果
let str = strMap[num]; //数字对应的字符串
for (let t of result) { //遍历上一循环生成的字符串列表
for (let s of str) { //遍历当前数字可能对应的字符
nextResult.push(t + s); //拼接字符串
}
}
result = nextResult; //替换原字符串列表
}
return result;
};
这种方法是我们能想到的最原始的,当然也是最暴力的解法,我们有没有好一点的方法呢?
答案肯定有?
DFS
题目的最终要求就是让对提供的字符串(S)中包含的数字以 数字出现的顺序为顺序 进行全排列。
因为每个数字有其映射关系,所以建立字典表进行快速查询。
DFS 状态树
只要不满足终止条件(要么遇到 0、1,要么当前组合的长度 与 S 的长度不一致),就继续去组合生成新的分支
digits = '232'
2 3 2
a => ada
a ->d ->b => adc
c => adc
a => aea
e ->b => aec
c => aec
a => afa
f ->b => afc
c => afc
b ...
c ...
BFS 状态树
每次都收集当前层的组合状态,并将这些组合状态 作为下一层的生成 新的组合的基准值
digits = '232'
2(abc)
[a,b,c]
↓
3(def)
[ad,ae,af,bd,be,bf,cd,ce,cf]
↓
2(abc)
[ada,aea,afa, ....]
代码:
/**
* @param {string} digits
* @return {string[]}
*/
var letterCombinations = function(digits) {
if(digits.length === 0) {
return []
}
const dictionary = {
2: 'abc',
3: 'def',
4: 'ghi',
5: 'jkl',
6: 'mno',
7: 'pqrs',
8: 'tuv',
9: 'wxyz'
}
let result = [];
const dfs = (str, index) => {
// 当 index 等于 原始字符串时,说明当前分支已经组合完毕
// 可以结束递归了
if(index >= digits.length) {
result.push(str)
return
}
// 获取当前数字对应的 字母集合
let map = dictionary[digits[index]];
// 处理 1 、0 等异常边界
if(map) {
// 当前字母集合中的每一个元素和上层递归中生成的更多的字符组合
for(let i of map) {
// 需要加入组合的位置向后移动
dfs(str+i, index+1);
}
}
}
// 从字符串第一个位置开始组合
dfs('', 0);
return result
};
我们看看它的递归吧:第一次:dfs(’’,0)
进入for循环后会调用:dfs(a,1) dfs(‘b’,1) dfs(‘c’,1)
第三次的时候会调用:dfs(‘ad’,2) dfs(‘ae’,2) dfs(‘af’,2)
…
…
第四次:dfs(‘adg’,3) … …
最后当index为数字的长度的时候,说明都递归完了,那么它需要做的就是把每个dfs()的值push到结果集里面。
代码是不是看着很少,相对来说很清爽,只是需要理解。
所以在这种时候是推荐采用递归的。
坚持每天进步一点点,加油吧~
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)