深圳幻海软件技术有限公司 欢迎您!

面试热点题:回溯算法 电话号码的字母组合与组合总和

2023-03-31

前言:如果你一点也不了解什么叫做回溯算法,那么推荐你看看这一篇回溯入门,让你快速了解回溯算法的基本原理及框架电话号码的字母组合给定一个仅包含数字2-9的字符串,返回所有它能表示的字母组合。答案可以按任意顺序返回。给出数字到字母的映射如下(与电话按键相同)。注意1不对应任何字母。来源:力扣(LeetC

前言:
如果你一点也不了解什么叫做回溯算法,那么推荐你看看这一篇回溯入门,让你快速了解回溯算法的基本原理及框架

电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

来源:力扣(LeetCode)电话号码的字母组合


思路:
从示例来看,我们首先能想到的最直接的思路就是循环套循环来组合输出,但是循环会根据输入的数字越来越多导致套的层次越多,这时候我们就可以考虑学习上一篇的思路,使用递归回溯的办法解决。

  • 解决数字与字母映射的关系:
    我们可以直接定义一个数组,下标对应其字母:
vector<string> arr{ 
    "","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz" };
    //写两个空串是为了能用下标直接对应九宫格里的字母
  • 1
  • 2
  • 3
  • 画图举例
    我们以"234"来说明


下面我们来边写边解释:

class Solution {
public:
    vector<string> arr{
    "","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
    vector<string> _arr;//收集所有子集
    string tmp;//存储子集
    void BackTracking(string digits,int k)//k是记录遍历到第几个数字
    {
        if(tmp.size()==digits.size())//终止条件
        {
            _arr.push_back(tmp);//存放子集到总集合
            return;
        }
        //digits[k]-'0'是将数字字符改成数字
        //arr[digits[k]-'0']访问数字字符对应下标位置的字符串
        for(int i=0;i<arr[digits[k]-'0'].size();i++)
        {
            tmp.push_back(arr[digits[k]-'0'][i]);//收集字符
            BackTracking(digits,k+1);//递归,k+1为下一次进入的字符串
            tmp.pop_back();//回溯,撤销节点
        }
    }
    vector<string> letterCombinations(string digits) {
        if(digits.size()==0)
        {
            return _arr;
        }
        BackTracking(digits,0);
        return _arr;
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

组合总和

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

来源:力扣(LeetCode)组合总和


思路:
本题的主要关键信息是:所有数字不相同且可以无限制使用,组合与组合不相同,提示中已经表明不会出现0,所以我们直接忽略0带来的结果。

  • 我们以示例2来[2,3,5]画图:


我们将candidates为升序排列,
由于数字的数量不做限制,我们每次for循环开始的位置就可以是上次循环变量i所在的位置,而递归的深度限制就是总和达到target或者大于它的时候

下面我们边写边解释:

class Solution {
public:
    vector<vector<int>> arr;//收集符合条件子集
    vector<int> _arr;//存放单个子集
    //sum为_arr子集的总和数 begin为循环开始位置
    void BackTracking(vector<int>& candidates,int target,int sum,int begin)
    {
        if(sum>target)//终止条件
        {
            return;
        }
        if(sum==target)
        {
            arr.push_back(_arr);
            return;
        }
        for(int i=begin;i<candidates.size();i++)
        {
            sum+=candidates[i];//递加总和
            _arr.push_back(candidates[i]);//存放至子集
            BackTracking(candidates,target,sum,i);//i不加1 表示可以重复取该数
            sum-=candidates[i];//回溯,撤销上一次的运算
            _arr.pop_back();//回溯,撤销上一次的运算
        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        sort(candidates.begin(),candidates.end());//先排序,后续更好优化
        BackTracking(candidates,target,0,0);
        return arr;
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

剪枝优化

优化后的代码:

class Solution {
public:
    vector<vector<int>> arr;
    vector<int> _arr;
    void BackTracking(vector<int>& candidates,int target,int sum,int begin)
    {
        if(sum==target)
        {
            arr.push_back(_arr);
            return;
        }
        //如果sum+candidates[i]>target遍历可以停止了
        for(int i=begin;i<candidates.size() && sum+candidates[i]<=target;i++)
        {
            sum+=candidates[i];
            _arr.push_back(candidates[i]);
            BackTracking(candidates,target,sum,i);
            sum-=candidates[i];
            _arr.pop_back();
        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        sort(candidates.begin(),candidates.end());
        BackTracking(candidates,target,0,0);
        return arr;
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

这个题相较与回溯入门中的两个组合总和题
主要区别是没有组合里元素数量限制单个元素数量限制

文章知识点与官方知识档案匹配,可进一步学习相关知识
算法技能树首页概览42577 人正在系统学习中