当深度优先搜索(DFS)遇上数字字母组合问题【DFS,回溯法】

当深度优先搜索(DFS)遇上数字字母组合问题

问题描述

给定一个仅包含数字 2-9 的字符串,你需要返回所有它能表示的字母组合。每个数字可以表示对应数字上字母的任意一种组合。注意,1 不对应任何字母。

以下是问题的示例:

示例 1:

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

示例 2:

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

示例 3:

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

解决思想

为了解决这个问题,我们可以使用 深度优先搜索(DFS) 的方法,从头到尾逐个处理输入数字的每一位,生成所有可能的字母组合。我们会维护一个映射表,将数字与字母的对应关系保存起来,然后使用递归的方式探索所有可能的组合。

思路历程

  1. 建立映射表: 我们首先需要建立一个映射表,将数字与字母集合对应起来。这将帮助我们在处理每个数字时,知道可以选择哪些字母。可以使用std::map或数组来实现这个映射表。

  2. 递归生成组合: 我们将编写一个递归函数来处理生成字母组合的过程。这个函数会有几个参数:当前数字索引、当前构建的字符串和结果集。我们从索引0开始,递归地处理每个数字。

  3. 递归终止条件: 在递归函数内部,我们首先检查当前数字索引是否等于输入字符串的长度。如果是,说明我们已经处理完所有数字,将当前构建的字符串添加到结果集中,然后返回。

  4. 处理当前数字: 如果当前索引没有达到终止条件,我们需要获取当前数字对应的字母集合。然后,对于每个字母,我们都将它添加到当前构建的字符串末尾,然后递归调用函数,处理下一个数字(递增索引)。

  5. 获取字母集合: 我们可以通过之前建立的映射表来获取当前数字对应的字母集合。在遍历字母集合时,每次都将选定的字母添加到当前构建的字符串中,然后递归处理下一个数字。

  6. 结果集合并: 在递归的过程中,会不断地构建字符串,直到遍历完所有数字。这样,最终结果集中就会包含所有可能的字母组合。

代码实现:

class Solution {
    
    
public:
    map<char , string> digitToLetters = {
    
    
        {
    
    '2', "abc"},
        {
    
    '3', "def"},
        {
    
    '4', "ghi"},
        {
    
    '5', "jkl"},
        {
    
    '6', "mno"},
        {
    
    '7', "pqrs"},
        {
    
    '8', "tuv"},
        {
    
    '9', "wxyz"}
    };

    void generateCombinations(string digits , int index , string current , vector<string> &ans){
    
    
        if(index == digits.length()){
    
    
            ans.push_back(current);
            return ;
        }

        char now = digits[index];
        string nowStr = digitToLetters[now];

        for(char a : nowStr){
    
    
            generateCombinations(digits , index + 1 , current + a , ans);
        }

    }

    vector<string> letterCombinations(string digits) {
    
    
        vector<string> ans;

        if(digits.empty()){
    
    
            return ans;
        }

        generateCombinations(digits , 0 , "" , ans);

        return ans;
    };
};

可能令人迷惑的问题

1. 为什么使用深度优先搜索?

深度优先搜索是一种递归的算法,它能够在问题的状态空间中搜索所有可能的解。在本问题中,我们需要从左到右处理每个数字,并逐个选择可能的字母,然后在下一层递归中继续处理后面的数字。深度优先搜索很适合解决这种需要逐步选择的组合问题。

2. 如何处理空输入?

在输入为空的情况下,我们希望返回一个空的结果集。在函数开头,我们可以通过检查输入长度是否为零来处理这种情况,并在这种情况下直接返回空的结果集。

3. 为什么可以使用index == digits.length()条件作为递归出口呢?

使用条件index == digits.length()作为递归出口的原因是因为我们要处理的是从0到digits.length() - 1的数字索引,这代表了字符串中的每一个数字。

在深度优先搜索的过程中,我们首先处理的是索引为0的数字,然后递归处理索引为1的数字,以此类推。当我们处理到索引等于字符串长度时,意味着我们已经处理完了所有数字。考虑到字符串索引是从0开始的,如果索引等于字符串长度,说明我们已经处理过了整个字符串中的所有数字。

使用index == digits.length()作为递归出口的好处是,它可以确保我们在递归的过程中逐步处理每个数字,并在处理完最后一个数字后终止递归。这样,我们就能够生成所有可能的字母组合,并且在每个递归步骤中都会构建正确的字符串。

4.和回溯法区别
回溯法和DFS在这个问题中几乎是等价的,因为它们的核心思想都是深度搜索。在实际操作中,回溯法可能会在选择时进行一些剪枝优化,以减少不必要的搜索。

  • 回溯法的实现在函数命名上更体现了“回溯”的思想;
  • 而DFS则是传统的深度优先搜索

在代码实现上,它们的逻辑几乎一样,都是通过递归来遍历数字和字母的组合。

回溯法通常被用于更加复杂的组合问题,其中需要在选择路径时进行条件判断,而DFS更加通用,适用于各种类型的深度搜索问题。在这个特定的问题中,两者可以互换使用。

猜你喜欢

转载自blog.csdn.net/qq_22841387/article/details/132196670