🍿本节主题:回溯算法
🎈更多算法:深入聊聊KMP算法
💕我的主页:蓝色学者的个人主页
文章目录
- 一、前言
- 二、概念
- 三、例题
- 1.题目:全排列
- 2.解题思路
- 回溯算法的本质
- 问题1:
- 问题2:
- 问题3:
- 3.总结
- 4.参考代码
- 四、作业
- 五、结语
一、前言
很开心又跟大家见面了,今天我们来一起学习一下回溯算法的基本思想,完成一道经典题目的讲解(全排列),之后我还会留下本节课的小作业,感兴趣的话一起来看看把~
二、概念
回溯算法,又称试探法,外文名(backtrackingalgorithm)这也是为什么我们经常在定义回溯函数的时候为函数取名为 backtracking ,回溯算法正如其名,在有多种选择的情况下,回溯算法会试探性的挨个选择,接着再回退到选择前的状态。
其实,回溯算法本质上也是一个暴力算法
为什么说回溯算法本质上也是暴力算法呢,我们通过接下来的一个例题来为大家解释一下
三、例题
1.题目:全排列
全排列:leetcode链接:https://leetcode.cn/problems/permutations/
本题给定我们一个不含重复数字的数组 nums ,返回其 所有可能的全排列可以按任意顺序返回答案。
示例1:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例2:
输入:nums = [0,1]
输出:[[0,1],[1,0]]
2.解题思路
全排列想必大家在初高中都应该接触过,那么我们都会使用一种方法:定住一项,让后面交换位置,从而达到目的,比如:
定住1那么还有2和3,一种结果是{1,2,3} ,交换位置得{1,3,2}
定住2那么还有1和3,一种结果是{2,1,3} ,交换位置得{2,3,1}
定住3那么还有1和2,一种结果是{3,1,2} ,交换位置得{3,2,1}
这样我们便得到了{1,2,3}的全排列
但是假如我们要求:{1,2,3,4}的全排列呢?
很显然我们交换元素的逻辑就会变得复杂,2和3交换完以后,4怎么办?很显然这样会让我们大脑短路,如何才能求出呢?
回溯算法引入
回溯算法的本质
其实回溯算法本质上也是一种暴力的算法,他解决了我们在交换问题上的困惑,还拿刚刚{1,2,3}的例子:
如图,我将上述过程整理成了一张图片,方便大家学习感悟,在这里,我想问大家两个问题:
1.第一次取1,第二次怎么取2?第三次怎么取3?
2.什么时候该回溯?
3.回退到哪里?
相信这两个问题搞清楚了,你的回溯算法就算是听懂了,下面我来一一回答一下这三个问题:
问题1:
我们可以定义一个循环让此循环能够遍历完{1,2,3}
for(int i = 0;i<n;i++)
for(int i = first;i<n;i++)
1.我们只需要每次让下标为i位置的元素与第一个元素交换即可,这样我们不就能拿到分别以1,2,3开头的元素了吗,当我们确定了第一个元素,剩下的元素应当也按照这样的逻辑进行交换,注意此时就不再是与0号下标交换,而是与第一个不确定位置的下标交换,也就是first
2.在固定好一个数字后,我们需要依次确定其他数字,所以要重复上述过程,直到所有数字都被固定了,因此我们采用递归的思路来完成。
问题2:
当什么时候应该回溯呢?按照上文的逻辑,我们不难得出当得到与原数组相同长度的数组时,我们就可以保存结果,向上回退了
问题3:
回退到哪里?比如我们确定1后,选择了2,我们自然能确定{1,2,3},但我们还漏下了选择3之后,确定{1,3,2}的情况,自然要回退到没选择2的时候,想想我们第一个问题,不难得出,我们交换后,还要再交换回来,这是回溯算法的精髓
3.总结
这道题目是一道很经典的回溯算法题目,先把上面我回答的三个问题看清楚,再去挑战这道题会轻松很多,如果你对哪一步仍有疑问,可以试着多看一下我上面画的图,多回想上面的三个问题,一定会有所收获。
4.参考代码
class Solution {
public:
void backtrack(vector<vector<int>>& res, vector<int>& output, int first, int len)
{
// 到达数组最大长度
if (first == len)
{
res.emplace_back(output);
return;
}
for (int i = first; i < len; ++i)
{
// 交换
swap(output[i], output[first]);
// 递归确定下一个数
backtrack(res, output, first + 1, len);
// 交换回来
swap(output[i], output[first]);
}
}
vector<vector<int>> permute(vector<int>& nums)
{
vector<vector<int> > res;
backtrack(res, nums, 0, nums.size());
return res;
}
};
四、作业
✨消化吸收完本次讲解的内容后,大家可以试着去挑战两道题目
🎉如果你还未熟练掌握回溯算法:组合问题https://leetcode.cn/problems/combinations/
🎉如果你已熟练掌握了回溯算法:N皇后
https://leetcode.cn/problems/n-queens/
🎶之后的回溯练习文章里我还会详细讲解这两道题,期待的话可以点一下收藏
五、结语
到这里,我们就学完了回溯算法里最经典的题目,我们知道回溯算法其实也是一种暴力算法,其实对于有些问题,能够暴力写出就已经很不错了,比如N皇后问题,因此回溯算法也是很有必要掌握的
如果你感觉有所收获的话,可以 点赞+评论+收藏支持学者哦下次见