LeetCode之HOT100--017 电话号码的字母组合

1,256 阅读2分钟

「这是我参与11月更文挑战的9天,活动详情查看:2021最后一次更文挑战」。

前言

一直都计划学习数据结构与基本算法,但是平时都看一阵停一阵。现在决心坚持下去,我准备从LeetCode的HOT100开始,每天完成1~2道习题,希望通过这种方式养成持续学习的习惯。因为我是做iOS开发的,主要是用Objective-C语言,最近也在学习Swift,所以本系列的题解都将使用swift语言完成,本文更新的是LeetCode中HOT100的第9题017 电话号码的字母组合。

题目

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

17_telephone_keypad.png

示例 1:

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

示例 2:

输入:digits = ""
输出:[]

示例 3:

输入:digits = "2"
输出:["a","b","c"]

提示:

0 <= digits.length <= 4
digits[i] 是范围 ['2', '9'] 的一个数字。

分析

本题的目标是求所有字符的全排列组合,而且也给出了每一个数组对应的字符集,所以当题目中出现 “所有组合” 等类似字眼时,我们第一感觉就要想到用回溯。
下图是输入'23'时对应的输出结果,我们发现其实这有点类似树的结构,我们要做的就是对这棵树做深度优先算法进行遍历,找出所有长度为2(即输入字符串长度)路径组合。
02b0ec926e3da5f12a0a118293b8ac10dc236741ccb04414ded44a30f7fc70af-1573829897(1).jpg.png 所以本题采用深度优先算法(DFS)进行,基本思路如下:

1. 初始化当前下标curIndex = 0,当前组合子串为空串subStr = ""
2. 如果当前下标curIndex没有超出数字串digits的范围,则获取curIndex对应数字的字符集curMatch;
   否则表示深度已经最大,将subStr添加到结果集,over
3. 将curMatch中的第一个元素元素添加到subStr中,添加元素后递归步骤2,调用时将curIndex后移一位,递归调用步骤2后将subStr中的最后一个元素删除,添加curMatch中的下一个元素

题解

class KLLC017 {

    func letterCombinations(_ digits: String) -> [String] {
        if digits.count == 0 {
            return [String]()
        }
        // 建立字符和字符匹配数组的关联
        let matches = [
            "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"]
        ];
        //初始化 curIndex  curSubStr
        var result = [String]()
        var curSubStr = ""
        let curIndex = digits.startIndex
        //dfs调用,从第一个字符开始
        dfs(&curSubStr, digits, curIndex, matches, &result)

        return result
    }
    
    //dfs算法
    func dfs(_ curSubStr:inout String, _ digits:String, _ curIndex:String.Index, _ matches:Dictionary<String, [String]>, _ result:inout [String]){
        //如果当前下标超出字符范围,则over,保存结果
        if curIndex == digits.endIndex {
            let subStr = curSubStr
            result.append(subStr)
            return
        }
        
        //没有超出范围则继续,获取当前下标对应的 数字 以及 数字对应的匹配字符数组
        let curDigit = String(digits[curIndex])
        let curMatch = matches[curDigit] ?? [String]()
        // 依次将匹配字符加入curSubStr
        for ch in curMatch {
            //将当前字符加入curSubStr
            curSubStr.append(ch)
            //递归调用,进行下一位的处理
            dfs(&curSubStr, digits, digits.index(after: curIndex), matches, &result)
            //从curSubStr中移除最后一位,即移除刚刚添加的匹配字符,为当前数字对应的下一匹配字符加入curSubStr,同一个数字对应的匹配字符只能添加一次
            curSubStr.removeLast()
        }
    }
}