代码随想录一刷心得之回溯算法篇

题外话:四天时间刷完回溯算法,不知道这种速度算不算快哈哈哈,不过对于第一次接触回溯的小白来说,很多题目都很烧脑,重复行程坐飞机这道题把我卡了一个晚上

回溯算法

一种搜索方式,回溯是递归的副产品,只要有递归就会有回溯,回溯的本质是穷举所有可能

涉及题目类型

  • 组合:N个数里面按一定规则找出k个数的集合
  • 分割:一个字符串按一定规则有几种切割方式
  • 子集:一个N个数的集合里有多少符合条件的子集
  • 排列:N个数按一定规则全排列,有几种排列方式
  • 棋盘:N皇后,解数独等等

区别:组合不强调顺序,排列强调顺序
回溯法一般是在集合中递归搜索,集合的大小构成了树的宽度,递归的深度构成的树的深度,回溯法解决的问题都可以抽象为树形结构(N叉树)

卡神的模板真的强

void backtracking(参数) {//模板
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

一些知识点
substr(startIndex,i - startIndex + 1);//从startIndex开始,截取长度为i-startInedex+1的部分字符
private代表类内私有成员,仅有类内函数可以访问private,类外一切函数(包括继承者)均不可访问private成员。
求组合的情况

  • 一个集合来求组合的话,就需要startIndex,如1,12,123,2,23,3
  • 多个集合取组合,各个集合之间相互不影响,那么就不用startIndex,如**电话号码的字母组合**

在求和问题中,排序之后加剪枝是常见的套路!

重新安排行程

查了不少人写的注释说明,确实很不好理解,其中target指的就是map的value,即target.first也是个目的地,value是票的数量

//重新安排行程
class Solution {
private:
unordered_map<string, map<string, int>> targets;

// ticketNum是一共有多少张机票,result是最终的飞行路线结果
bool backtracking(int ticketNum, vector<string> &result)
{
    // 递归终止条件:如果飞行路线中机场个数 = 机票张数 + 1,则说明把所有机票都用完了,
    //             即收集到了一个可能路线
    if (result.size() == ticketNum + 1)
    {
        return true;
    }

// 开始递归:从上一次到达的机场,或者最初起飞的机场出发),遍历所有它可能飞往的达到机场,寻找一条路径
    for (pair<const string, int> &target : targets[result[result.size() - 1]])      
    {//target可以理解为一个map
        // 如果从当前机场,飞往到达机场的机票还有剩余,那么就还能飞
        if (target.second!=0)//还有目的地的飞机票
        {
            result.push_back(target.first); // 飞往到达机场,加到路线中
            target.second--;                // 当前机场->到达机场,所以机票张数-1
// 重要:递归调用,从下一个机场作为起飞点,再次寻找飞往的目的地
//     (1)如果调用下一个机场的结果是true,说明找到了一条路线,则直接返回
//     (2)如果调用下一个机场的结果是false,说明没有找到一条完整路线(即走到了死胡同里)
//        那么就到了下面回溯的过程:即把当前for循环选择的到达机场弹出,换另一个到达机场
            if (backtracking(ticketNum, result))
                return true;   // 找到一条路线就返回
            result.pop_back(); // 回溯
// 注意:这里一定不要忘了机票数量++,因为上面--是因为当前位置的到达机场为当前for循环选择的值
//      (假设是B)的时候,后面调用递归安排后面的路线。一旦后面递归完成,说明当前位置选择
//      这个for循环的值B经过测试不能满足要求了,所以要把当前选择的到达机场B弹出,然后把
//      它的机票张数也复原,然后重新选择另一个达到机场C,此时B的机票张数应该还是原来的张数
//      所以这里回溯的时候一定要把机票张数也++,对机票张数进行复原,说不定后面又有路线到B
            target.second++;
        }
    }
    return false;
}
public:
    vector<string> findItinerary(vector<vector<string>>& tickets) {
        vector<string> result;
//将tickets映射成有一个起始机场对应不同终点机场的情况
//unordered_map--key无序 map--key有序
// vec[0]是出发机场的名称,targets[vec[0]]就是取出键对应的值,即<到达机场,剩余机票次数>
        for(const vector<string>& vec:tickets){
//一种特殊写法,找出的这个数值对叫vec,每一次都更新,比较新的写法
            targets[vec[0]][vec[1]]++;由vec[0]起飞 vec[1]降落的飞机增加一个架次
        }
        result.push_back("JFK");
        backtracking(tickets.size(),result);
        return result;
    }
};

N皇后——不能同行,不能同列,不能同斜线

每一行每一列只放一个皇后,只需要一层for循环遍历一行,递归来遍历列,然后一行一列确定皇后的唯一位置,不可能有同样情况,所以不用去重。

递归函数的返回值需要是bool类型,为什么呢?

用void声明可以返回插入多个目标,如N皇后的多种解法,但bool只需要有一种解法即可

bool类型返回值只有true或者false,而void只是执行这段函数,没有任何返回值

如果是一个集合来求组合加粗样式的话,就需要startIndex,这是因为要去重
如(0,1)和(1,0)是一个东西,后面的数不能再用前面的数。

排列会用到used(方便些)used数组在时间复杂度上几乎没有额外负担!

放一张网友总结的思维导图,希望二刷这些题还能记得哈哈哈
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值