阅读视图

发现新文章,点击刷新页面。

C++总结了回溯问题类型 带你搞懂回溯算法(搜索篇)

在上一篇题解中,我总结了回溯算法的三种类型,以及什么时候用回溯算法,怎么写回溯算法,如果没看过的,强烈建议先看:C++ 总结了回溯问题类型 带你搞懂回溯算法(大量例题)

这一节,我们就来解析“搜索”类型的回溯问题。

为什么要单独分出一种“搜索”类型?

其实,“搜索”类型的题解中都能看到“子集”、“排列”类型题目的影子,只要你搞懂前面两种类型问题的本质,就很容易联想到了。“搜索”类型的问题最难的就是把问题抽象化!!大多数比赛或者面试题中不会直接出现“子集、排列、组合”等字眼,更不会直接让去求,而是通过某些包装,借助某个情景,让你一下子摸不着头脑,看不出应该使用哪种解法,本题就是最好的说明

解题步骤:
1.读懂题意,把题目尽可能抽象成“子集、排列、组合”类型问题
本题的题目总结而言就是:有十盏灯,我分别给他编号0-9,号码0-3代表小时,号码4-9代表分钟,然后每盏灯又代表着不同数字,如下图
image.png
(灯泡中的红色数字,其实就是二进制转换,题目中的图也有标)
然后从10盏灯中挑选n盏打开,问你有多少种组合,返回这些组合代表的时间。这样一翻译,是不是有点“组合”类型问题的味道了?说白了就是:从n个数里面挑选k个,返回组合。如果你能形成这种抽象思想,那么你就成功一大半了。

2.回溯六步走

  • ①画出递归树,找到状态变量(回溯函数的参数),这一步非常重要
  • ②根据题意,确立结束条件**
  • ③找准选择列表(与函数参数相关),与第一步紧密关联
  • ④判断是否需要剪枝**
  • ⑤作出选择,递归调用,进入下一层
  • ⑥撤销选择

3.开始解题
①递归树,这里用动画演示。假设n=2,即选两盏灯亮
<1.png,2.png,3.png,4.png,222.png,5.png,6.png,7.png,8.png,9.png,10.png,11.png,12.png>

我们接下来思考,回溯函数需要什么哪些参数,来表示当前状态。首先灯是越来越少的,所以需要一个参数num,表示剩余可亮的灯数,直到num等于0就应该结束;然后还需要参数start,来指示当前可选择的灯的开始位置,比如n=2我选了7号灯,然后剩下的一盏灯,只能选8号或9号,你不可能回去选0-6号,因为会导致重复,这个问题在“子集、组合”类型问题中说过,详细请看顶部的文章;最后还需要一个time参数,来表示当前状态的时间。算法签名如下

###cpp

unordered_map<int,int> hash={{0,1},{1,2},{2,4},{3,8},{4,1},{5,2},{6,4},{7,8},{8,16},{9,32}};//用一个hash表映射,灯对应的数字
void backtrack(int num,int start,pair<int,int>& time)//我用stl中的pair结构来统一管理小时和分钟,代码比较简洁,你也可以把他们拆成两个参数,hour和minute

②根据题意,确立结束条件
这个上面提到过了,当num=0就是没有灯可以点亮了,就应该return,加入结果集

###cpp

if(num==0)
        {
            if(time.first>11||time.second>59)//判断合法性,注意题目要求
                return;
            string temp_hour=to_string(time.first);
            string temp_minute=to_string(time.second);
            if(temp_minute.size()==1)//如果minute只有一位要补0,注意题目要求
                temp_minute.insert(0,"0");
            res.push_back(temp_hour+":"+temp_minute);//构造格式
            return;
        }

③找准选择列表
从参数start标识的位置开始,到最后一盏灯,都是可选的

###cpp

for(int i=start;i<10;i++)
{


}

④判断是否需要剪枝
当hour>11或minute>59的时候,无论还有没有灯可以点亮,都不用再继续递归下去,因为后面只会越增越多,应该剪枝

###cpp

for(int i=start;i<10;i++)
{
    if(time.first>11||time.second>59)
         continue;
}

⑤作出选择,递归调用,进入下一层

###cpp

 for(int i=start;i<10;i++)
    {
        if(time.first>11||time.second>59)
            continue;
        pair<int,int>store=time;//保存状态,用于回溯时,恢复当前状态
        if(i<4)//对小时数进行操作
            time.first+=hash[i];
        else//对分钟数进行操作
            time.second+=hash[i];
        backtrack(num-1,i+1,time);//进入下一层,注意下一层的start是i+1,即从当前灯的下一盏开始

⑥撤销选择
整体代码如下

###cpp

vector<string>res;
    unordered_map<int,int> hash={{0,1},{1,2},{2,4},{3,8},{4,1},{5,2},{6,4},{7,8},{8,16},{9,32}};
    void backtrack(int num,int start,pair<int,int>& time)
    {
        if(num==0)
        {
            if(time.first>11||time.second>59)//判断合法性
                return;
            string temp_hour=to_string(time.first);
            string temp_minute=to_string(time.second);
            if(temp_minute.size()==1)//如果minute只有一位要补0
                temp_minute.insert(0,"0");
            res.push_back(temp_hour+":"+temp_minute);//构造格式
            return;
        }
    
        for(int i=start;i<10;i++)
        {
            if(time.first>11||time.second>59)
                continue;
            pair<int,int>store=time;//保存状态
            if(i<4)
                time.first+=hash[i];
            else
                time.second+=hash[i];
            backtrack(num-1,i+1,time);//进入下一层,注意下一层的start是i+1,即从当前灯的下一盏开始
            time=store;//恢复状态
        }
    }
    vector<string> readBinaryWatch(int num) {
        pair<int,int>time(0,0);//初始化时间为0:00
        backtrack(num,0,time);
        return res;
    }
❌