更新图床
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
|
||||
<p align="center">
|
||||
<a href="https://programmercarl.com/other/xunlianying.html" target="_blank">
|
||||
<img src="../pics/训练营.png" width="1000"/>
|
||||
</a>
|
||||
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
|
||||
|
||||
|
||||
> 20张树形结构图、14道精选回溯题目,21篇回溯法精讲文章,由浅入深,一气呵成,这是全网最强回溯算法总结!
|
||||
|
||||
# 回溯法理论基础
|
||||
# 回溯法理论基础
|
||||
|
||||
转眼间[「代码随想录」](https://img-blog.csdnimg.cn/20200815195519696.png)里已经分享连续讲解了21天的回溯算法,是时候做一个大总结了,本篇高能,需要花费很大的精力来看!
|
||||
|
||||
@@ -49,9 +51,9 @@ void backtracking(参数) {
|
||||
|
||||
**事实证明这个模板会伴随整个回溯法系列!**
|
||||
|
||||
# 组合问题
|
||||
# 组合问题
|
||||
|
||||
## 组合问题
|
||||
## 组合问题
|
||||
|
||||
在[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)中,我们开始用回溯法解决第一道题目:组合问题。
|
||||
|
||||
@@ -61,17 +63,17 @@ void backtracking(参数) {
|
||||
|
||||
本题我把回溯问题抽象为树形结构,如题:
|
||||
|
||||

|
||||

|
||||
|
||||
可以直观的看出其搜索的过程:**for循环横向遍历,递归纵向遍历,回溯不断调整结果集**,这个理念贯穿整个回溯法系列,也是我做了很多回溯的题目,不断摸索其规律才总结出来的。
|
||||
|
||||
对于回溯法的整体框架,网上搜的文章这块都说不清楚,按照天上掉下来的代码对着讲解,不知道究竟是怎么来的,也不知道为什么要这么写。
|
||||
对于回溯法的整体框架,网上搜的文章这块都说不清楚,按照天上掉下来的代码对着讲解,不知道究竟是怎么来的,也不知道为什么要这么写。
|
||||
|
||||
**所以,录友们刚开始学回溯法,起跑姿势就很标准了!**
|
||||
|
||||
优化回溯算法只有剪枝一种方法,在[回溯算法:组合问题再剪剪枝](https://programmercarl.com/0077.组合优化.html)中把回溯法代码做了剪枝优化,树形结构如图:
|
||||
|
||||

|
||||

|
||||
|
||||
大家可以一目了然剪的究竟是哪里。
|
||||
|
||||
@@ -87,11 +89,11 @@ void backtracking(参数) {
|
||||
在[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)中,相当于 [回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)加了一个元素总和的限制。
|
||||
|
||||
树形结构如图:
|
||||

|
||||

|
||||
|
||||
整体思路还是一样的,本题的剪枝会好想一些,即:**已选元素总和如果已经大于n(题中要求的和)了,那么往后遍历就没有意义了,直接剪掉**,如图:
|
||||
|
||||

|
||||

|
||||
|
||||
在本题中,依然还可以有一个剪枝,就是[回溯算法:组合问题再剪剪枝](https://programmercarl.com/0077.组合优化.html)中提到的,对for循环选择的起始范围的剪枝。
|
||||
|
||||
@@ -113,7 +115,7 @@ void backtracking(参数) {
|
||||
|
||||
树形结构如下:
|
||||
|
||||

|
||||

|
||||
|
||||
最后还给出了本题的剪枝优化,如下:
|
||||
|
||||
@@ -123,7 +125,7 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target;
|
||||
|
||||
优化后树形结构如下:
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
### 组合总和(三)
|
||||
@@ -138,7 +140,7 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target;
|
||||
|
||||
都知道组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上“使用过”,一个维度是同一树层上“使用过”。**没有理解这两个层面上的“使用过” 是造成大家没有彻底理解去重的根本原因**。
|
||||
|
||||

|
||||

|
||||
|
||||
我在图中将used的变化用橘黄色标注上,**可以看出在candidates[i] == candidates[i - 1]相同的情况下:**
|
||||
|
||||
@@ -159,7 +161,7 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target;
|
||||
|
||||
树形结构如下:
|
||||
|
||||

|
||||

|
||||
|
||||
如果大家在现场面试的时候,一定要注意各种输入异常的情况,例如本题输入1 * #按键。
|
||||
|
||||
@@ -187,10 +189,10 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target;
|
||||
|
||||
树形结构如下:
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
# 子集问题
|
||||
# 子集问题
|
||||
|
||||
## 子集问题(一)
|
||||
|
||||
@@ -198,7 +200,7 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target;
|
||||
|
||||
如图:
|
||||
|
||||

|
||||

|
||||
|
||||
认清这个本质之后,今天的题目就是一道模板题了。
|
||||
|
||||
@@ -225,23 +227,23 @@ if (startIndex >= nums.size()) { // 终止条件可以不加
|
||||
|
||||
树形结构如下:
|
||||
|
||||

|
||||

|
||||
|
||||
## 递增子序列
|
||||
|
||||
在[回溯算法:递增子序列](https://programmercarl.com/0491.递增子序列.html)中,处处都能看到子集的身影,但处处是陷阱,值得好好琢磨琢磨!
|
||||
|
||||
树形结构如下:
|
||||

|
||||

|
||||
|
||||
|
||||
很多同学都会把这道题目和[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)混在一起。
|
||||
|
||||
**[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)也可以使用set针对同一父节点本层去重,但子集问题一定要排序,为什么呢?**
|
||||
**[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)也可以使用set针对同一父节点本层去重,但子集问题一定要排序,为什么呢?**
|
||||
|
||||
我用没有排序的集合{2,1,2,2}来举个例子画一个图,如下:
|
||||
|
||||

|
||||

|
||||
|
||||
**相信这个图胜过千言万语的解释了**。
|
||||
|
||||
@@ -257,7 +259,7 @@ if (startIndex >= nums.size()) { // 终止条件可以不加
|
||||
|
||||
如图:
|
||||
|
||||

|
||||

|
||||
|
||||
**大家此时可以感受出排列问题的不同:**
|
||||
|
||||
@@ -270,7 +272,7 @@ if (startIndex >= nums.size()) { // 终止条件可以不加
|
||||
|
||||
树形结构如下:
|
||||
|
||||

|
||||

|
||||
|
||||
**这道题目神奇的地方就是used[i - 1] == false也可以,used[i - 1] == true也可以!**
|
||||
|
||||
@@ -278,21 +280,21 @@ if (startIndex >= nums.size()) { // 终止条件可以不加
|
||||
|
||||
树层上去重(used[i - 1] == false),的树形结构如下:
|
||||
|
||||

|
||||

|
||||
|
||||
树枝上去重(used[i - 1] == true)的树型结构如下:
|
||||
|
||||

|
||||

|
||||
|
||||
**可以清晰的看到使用(used[i - 1] == false),即树层去重,效率更高!**
|
||||
|
||||
本题used数组即是记录path里都放了哪些元素,同时也用来去重,一举两得。
|
||||
|
||||
# 去重问题
|
||||
# 去重问题
|
||||
|
||||
以上我都是统一使用used数组来去重的,其实使用set也可以用来去重!
|
||||
|
||||
在[本周小结!(回溯算法系列三)续集](https://programmercarl.com/回溯算法去重问题的另一种写法.html)中给出了子集、组合、排列问题使用set来去重的解法以及具体代码,并纠正一些同学的常见错误写法。
|
||||
在[本周小结!(回溯算法系列三)续集](https://programmercarl.com/回溯算法去重问题的另一种写法.html)中给出了子集、组合、排列问题使用set来去重的解法以及具体代码,并纠正一些同学的常见错误写法。
|
||||
|
||||
同时详细分析了 使用used数组去重 和 使用set去重 两种写法的性能差异:
|
||||
|
||||
@@ -304,7 +306,7 @@ if (startIndex >= nums.size()) { // 终止条件可以不加
|
||||
|
||||
**使用set去重,不仅时间复杂度高了,空间复杂度也高了**,在[本周小结!(回溯算法系列三)](https://programmercarl.com/周总结/20201112回溯周末总结.html)中分析过,组合,子集,排列问题的空间复杂度都是O(n),但如果使用set去重,空间复杂度就变成了O(n^2),因为每一层递归都有一个set集合,系统栈空间是n,每一个空间都有set集合。
|
||||
|
||||
那有同学可能疑惑 用used数组也是占用O(n)的空间啊?
|
||||
那有同学可能疑惑 用used数组也是占用O(n)的空间啊?
|
||||
|
||||
used数组可是全局变量,每层与每层之间公用一个used数组,所以空间复杂度是O(n + n),最终空间复杂度还是O(n)。
|
||||
|
||||
@@ -316,7 +318,7 @@ used数组可是全局变量,每层与每层之间公用一个used数组,所
|
||||
|
||||
以输入:[["JFK", "KUL"], ["JFK", "NRT"], ["NRT", "JFK"]为例,抽象为树形结构如下:
|
||||
|
||||

|
||||

|
||||
|
||||
本题可以算是一道hard的题目了,关于本题的难点我在文中已经详细列出。
|
||||
|
||||
@@ -325,19 +327,19 @@ used数组可是全局变量,每层与每层之间公用一个used数组,所
|
||||
本题其实是一道深度优先搜索的题目,但是我完全使用回溯法的思路来讲解这道题题目,**算是给大家拓展一下思维方式,其实深搜和回溯也是分不开的,毕竟最终都是用递归**。
|
||||
|
||||
|
||||
# 棋盘问题
|
||||
# 棋盘问题
|
||||
|
||||
## N皇后问题
|
||||
## N皇后问题
|
||||
|
||||
在[回溯算法:N皇后问题](https://programmercarl.com/0051.N皇后.html)中终于迎来了传说中的N皇后。
|
||||
|
||||
下面我用一个3 * 3 的棋盘,将搜索过程抽象为一棵树,如图:
|
||||
|
||||

|
||||

|
||||
|
||||
从图中,可以看出,二维矩阵中矩阵的高就是这棵树的高度,矩阵的宽就是树形结构中每一个节点的宽度。
|
||||
|
||||
那么我们用皇后们的约束条件,来回溯搜索这棵树,**只要搜索到了树的叶子节点,说明就找到了皇后们的合理位置了**。
|
||||
那么我们用皇后们的约束条件,来回溯搜索这棵树,**只要搜索到了树的叶子节点,说明就找到了皇后们的合理位置了**。
|
||||
|
||||
如果从来没有接触过N皇后问题的同学看着这样的题会感觉无从下手,可能知道要用回溯法,但也不知道该怎么去搜。
|
||||
|
||||
@@ -361,7 +363,7 @@ used数组可是全局变量,每层与每层之间公用一个used数组,所
|
||||
|
||||
因为这个树形结构太大了,我抽取一部分,如图所示:
|
||||
|
||||

|
||||

|
||||
|
||||
解数独可以说是非常难的题目了,如果还一直停留在一维递归的逻辑中,这道题目可以让大家瞬间崩溃。
|
||||
|
||||
@@ -380,22 +382,27 @@ used数组可是全局变量,每层与每层之间公用一个used数组,所
|
||||
以下在计算空间复杂度的时候我都把系统栈(不是数据结构里的栈)所占空间算进去。
|
||||
|
||||
子集问题分析:
|
||||
|
||||
* 时间复杂度:O(2^n),因为每一个元素的状态无外乎取与不取,所以时间复杂度为O(2^n)
|
||||
* 空间复杂度:O(n),递归深度为n,所以系统栈所用空间为O(n),每一层递归所用的空间都是常数级别,注意代码里的result和path都是全局变量,就算是放在参数里,传的也是引用,并不会新申请内存空间,最终空间复杂度为O(n)
|
||||
|
||||
排列问题分析:
|
||||
|
||||
* 时间复杂度:O(n!),这个可以从排列的树形图中很明显发现,每一层节点为n,第二层每一个分支都延伸了n-1个分支,再往下又是n-2个分支,所以一直到叶子节点一共就是 n * n-1 * n-2 * ..... 1 = n!。
|
||||
* 空间复杂度:O(n),和子集问题同理。
|
||||
|
||||
组合问题分析:
|
||||
|
||||
* 时间复杂度:O(2^n),组合问题其实就是一种子集的问题,所以组合问题最坏的情况,也不会超过子集问题的时间复杂度。
|
||||
* 空间复杂度:O(n),和子集问题同理。
|
||||
|
||||
N皇后问题分析:
|
||||
N皇后问题分析:
|
||||
|
||||
* 时间复杂度:O(n!) ,其实如果看树形图的话,直觉上是O(n^n),但皇后之间不能见面所以在搜索的过程中是有剪枝的,最差也就是O(n!),n!表示n * (n-1) * .... * 1。
|
||||
* 空间复杂度:O(n),和子集问题同理。
|
||||
|
||||
解数独问题分析:
|
||||
|
||||
* 时间复杂度:O(9^m) , m是'.'的数目。
|
||||
* 空间复杂度:O(n^2),递归的深度是n^2
|
||||
|
||||
@@ -403,7 +410,7 @@ N皇后问题分析:
|
||||
**一般说道回溯算法的复杂度,都说是指数级别的时间复杂度,这也算是一个概括吧!**
|
||||
|
||||
|
||||
# 总结
|
||||
# 总结
|
||||
|
||||
**[「代码随想录」](https://img-blog.csdnimg.cn/20200815195519696.png)历时21天,14道经典题目分析,20张树形图,21篇回溯法精讲文章,从组合到切割,从子集到排列,从棋盘问题到最后的复杂度分析**,至此收尾了。
|
||||
|
||||
@@ -412,11 +419,12 @@ N皇后问题分析:
|
||||
可以说方方面面都详细介绍到了。
|
||||
|
||||
例如:
|
||||
|
||||
* 如何理解回溯法的搜索过程?
|
||||
* 什么时候用startIndex,什么时候不用?
|
||||
* 如何去重?如何理解“树枝去重”与“树层去重”?
|
||||
* 什么时候用startIndex,什么时候不用?
|
||||
* 如何去重?如何理解“树枝去重”与“树层去重”?
|
||||
* 去重的几种方法?
|
||||
* 如何理解二维递归?
|
||||
* 如何理解二维递归?
|
||||
|
||||
**这里的每一个问题,网上几乎找不到能讲清楚的文章,这也是直击回溯算法本质的问题**。
|
||||
|
||||
@@ -424,11 +432,11 @@ N皇后问题分析:
|
||||
|
||||
此时回溯算法系列就要正式告一段落了。
|
||||
|
||||
**录友们可以回顾一下这21天,每天的打卡,每天在交流群里和大家探讨代码,最终换来的都是不知不觉的成长**。
|
||||
**录友们可以回顾一下这21天,每天的打卡,每天在交流群里和大家探讨代码,最终换来的都是不知不觉的成长**。
|
||||
|
||||
同样也感谢录友们的坚持,这也是我持续写作的动力,**正是因为大家的积极参与,我才知道这件事件是非常有意义的**。
|
||||
|
||||
回溯专题汇聚为一张图:
|
||||
回溯专题汇聚为一张图:
|
||||
|
||||

|
||||
|
||||
|
||||
Reference in New Issue
Block a user