算法通过村第十八关-回溯|青铜笔记|什么叫回溯(初篇)


前言


提示:我对你透露一个大密码,这是人类最古老的玩笑。往哪走,都是往前走。 --米兰·昆德拉

回溯是非常重要的算法思想之一,主要解决一些暴力枚举也搞不定的问题(这里埋个坑💣)例如组合、分割、子集、棋盘等等。从性能角度来看回溯算法的效率并不是很高,但是对于暴力也解决不了的问题,它往往很快可以出结果,效率低就可以理解了吧。接下来,就看看回溯的事情吧🤩

回溯可以视为递归的拓展,很多思想和解法都和递归密切相关,再很多材料中都将回溯与递归(一同解释)

,后续我们会遇到路径问题,这就是很典型的问题(相结合的形式),学习回溯时,可以更好的理解递归,相反也是一样的。

关于递归和回溯,这里也区分一下,假设一个场景是这样的:我想脱单怎么办

  • 递归策略:先与意中人制造偶遇,然后了解人家的情况,然后约人家吃饭,有好感以后尝试拉手,如果没拒绝,就尝试表白啦。
  • 回溯策略:先统计周围所有的单身女孩,然后一个一个表白,被拒绝就说“我喝醉了”,然后就当什么事情也没发生,继续找下一个。

其实回溯的本质就是这么一个过程,好好想一下,看看有没有感觉~。

回溯最大的好处就是有非常明确的模板,所以这里的回溯都是有一个大框架,因此透彻理解回溯的框架是解决一切回溯问题的基础。所以这里做到是就是分析这个框架,让你彻底搞明白。

当然回溯并不是万能的,而且解决问题也是非常明确,例如:

  1. 组合
  2. 分割
  3. 子集
  4. 排序
  5. 棋盘

不过这些问题,在处理的时候具体措施也有不同,这里我们接着往下看。

回溯可以理解为递归的扩展,代码层面上又很像深度遍历的N叉树,因此只要知道递归,理解回溯并不难,难在很多人不理解为什么在递归之后会有个“撤销”的动作。这里通过图示给你解释清楚,这里假设一个场景,如果你这个时候谈新女朋友了,来你家之前,你时候会将你前任的东西藏起来?回溯就是这样,有些信息是前任的,需要处理掉,重新开始才行。

回溯最让人激动的是有非常清晰的解题模板,如下所示,大部分的回溯代码的框架都是这个样子,具体的我们后面在解释:

void backtracking(参数){
	if(终止条件){
		存放结果;
		return;
	}
	for(选择本层集中元素(画成树,就是树节点孩子的大小)){
		处理节点;
		backtracking();
		回溯,撤销处理结果;
	}
}

回溯是有明确的解题模板,下面我们就具体分析一下。

从N叉树说起

解释之前,还是看看N叉树的遍历吧,我们知道二叉树的前序过程:

void treeDFS(TreeNode root) {
	if(root == null){
		return;
	}
	system.out.println(root.val);
	treeDFS(root.left);
	treeDFS(root.right);
}

class TreeNode{
	int val;
	TreeNode left;
	TreeNode right;
}

这里如果是三叉树,四叉树甚至N叉树该怎么处理呢?很显然这里不能用left和right来表示分支了,使用一个List比较好,也就是下面这个例子:

class TreeNode{
	int val;
	List<TreeNode> nodes;
}

代码就需要改一下了:

public statis void treeDFS(TreeNode root) {
	// 递归的必要终止条件
	if(root == null){
		return;
	}
	// 处理节点
	system.out.println(root.val);
	// 通过循环,分别遍历N个字数
	for(int i = 1; i <= node.length; i++){
		treeDFS("第i个字节点");
	}
}

到这里,你有没有发现已经很相似了,说明两者之间确实存在某种必要的联系。其他的咱们暂时不管,我们是否已经记住刚才的那个框架了。(N叉树的遍历)

为什么有的问题暴力搜索也不行

试想一下,回溯主要解决暴力枚举处理不了的问题,为什么这么神奇,暴力解决不了?

参考题目介绍:77. 组合 - 力扣(LeetCode)

在这里插入图片描述

这里说先明确一点:如果n=4,k=2.那就是说从4个数种选择2个,问你最后能选出多少组数据。

这里类似高中的一个数学题:大致说一下过程,如果数字n=4,能用的数字就是{1,2,3,4}

  1. 先取出一个1,则有[1,2]、[1,3]、[1,4]三种可能。
  2. 再取出一个2,1已经取过了,不能再取了,则可以取[2,3]、[2,4]两种可能。
  3. 再取一个3,1和2都已经取过了,不能再取了,这里就剩下[3,4]一种可能了。
  4. 取4,以为1,2,3都已经取过了,所以就直接返回null。
  5. 最后的结果就是[1,2]、[1,3]、[1,4、[2,3]、[2,4]、[3,4]

这里我们思考下该问题要怎么实现,假如只有两个数,采用双层循环就可以了:

int n = 4;
for(int i = 1; i <= n; i++){
	for(int j = i + 1; j <= n; j++){
		system.out.println(i + " "+j);
	}
}

那如果n和k都变大,比如说n=200,k是3呢?也可以采用三层循环搞定:

int n = 200;
for(int i = 1; i <= n; i++){
	for(int j = i + 1; j <= n; j++){
		for(int l = j + 1; l <= n; l++){
			system.out.println(i + " "+j +" " + l);
		}
	}
}

如果这里的k是5呢?如果更大呢?这里就不好写了吧?甚至告诉你k就是一个未知的正整数k,你需要怎么写循环呀,是不是无解了,这里已经无能为力的吧,这里就是说暴力搜素不能解决。

这就是组合类型问题,除此以外子集、排序、切割、棋盘等方面都有类似的问题,我们可以好好学习一下。


总结

提示:回溯算法;初始回溯;什么叫回溯;回溯的套路;回溯的核心问题


如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ ("▔□▔)/

如有不理解的地方,欢迎你在评论区给我留言,我都会逐一回复 ~

也欢迎你 关注我 ,喜欢交朋友,喜欢一起探讨问题。

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

师晓峰

啤酒饮料矿泉水,你的打赏冲一冲

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值