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

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

电话号码的字母组合

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

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

来源:力扣(LeetCode)电话号码的字母组合
在这里插入图片描述
在这里插入图片描述
思路:
从示例来看,我们首先能想到的最直接的思路就是循环套循环来组合输出,但是循环会根据输入的数字越来越多导致套的层次越多,这时候我们就可以考虑学习上一篇的思路,使用递归回溯的办法解决。

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

组合总和

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

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

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

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

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

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

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

扫描二维码关注公众号,回复: 15387146 查看本文章

下面我们边写边解释:

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;
    }
};

剪枝优化

在这里插入图片描述

优化后的代码:

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;
    }
};

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

猜你喜欢

转载自blog.csdn.net/DEXTERFUTIAN/article/details/129657634