电话号码的字母组合

给定一个仅包含数字 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到结果集里面。

代码是不是看着很少,相对来说很清爽,只是需要理解。

所以在这种时候是推荐采用递归的。

坚持每天进步一点点,加油吧~

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐