回溯法
回溯法:暴力解法,遍历问题的所有方案不行就返回。即遍历+递归。
常见的问题:
- 组合问题:N个数里面按一定规则找出k个数的集合。
- 排列问题:N个数按照一定规则全排列
- 切割问题:一个字符串按照一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 棋盘问题:N皇后,数独问题。
解题模板
可以讲回溯的题目抽象为树形结构,便于理解
void backtracking(参数){
if(终止条件)
{
存放结果;
return;
}
for(选择:本层集合中元素)
{
处理节点;
backtracking(参数,选择列表);//递归
撤销处理//回溯
}
}
-
组合问题
从一组数中选择满足条件的k个数。 -
Leetcode 77.组合(baseline)
-
Leetcode 39.组合总和(media)
在无重复数组中找出一组数,使其结果为target.数组中每个数可以重复使用!!! -
Leetcode 40.组合总和||(hard)
给定一个数组(可能含有重复数),找出一组数,其和为target.涉及去重问题!!! -
Leetcode 703.组合总和|||(easy)
给定一个数包含(1-k,不重复),找出和为target的一组数,每个数不可重复使用!!! -
多个集合求组合
Leetcode 17.电话号码的字母组合
解组合问题,其搜索过程是for循环横向遍历(每个解集),递归纵向遍历(每个解集包含的答案)。
剪枝问题:在求解过程,很多答案是没必要搜索的,可根据题目的条件添加一些限制项,避免不必要的遍历。可以画树形结构图细化问题。
组合问题常涉及startIndex即开始坐标,例如,Leetcode 40,703,77,在树形结构中表示下一个开始搜索的下标,以避免出现解集重复问题。
但也有例外,如Leetcode39中,每个数可以重复使用,这样循环从0开始。
去重问题:给定数组中存在重复数时,就涉及去重问题,即使同一解集中可以存在重复的数例如[1,3,2,3].但是不同解集中不能有相同答案。例如解集A为[1,2,3,2],解集B为[1,3,2,2].在解集去重问题时,应该先对数组进行排列,然后定义一个数组来标记每个元素是否使用过。
组合问题的终止条件:题目要求的,例如当数组的个数为k时,或者数组中的和为target时返回。 -
切割问题
把给定的字符串划分为子集,按一定规则重新组合起来- Leetcode 131.分割回文串
如何分割将字符串分为不同的子串(回溯)
然后按题目要求判断合法性 - Leetcode 0093.恢复IP地址
- Leetcode 131.分割回文串
-
子集问题
给定一个数组,求其所有子集。- Leetcode 78 子集
- Leetcode 0090 .子集||—数组存在重复元素,要去重
- Leetcode 491.递增子序列
求递增子序列与子集问题相似,都是求子节点。但是数组存在重复数,不能像leetcode 90中直接排序后,用数组标记是否使用过去重,因为这样会打乱顺序。
组合和分割问题都是求树的叶子节点,而子集问题是找树的所有节点。
终止条件不同
-
排列问题
求给定数组的有多少中排列方式,排列是有序的,也就是说[1,2]和[2,1]是两种排列方式。但是排列涉及到去重问题,避免在同一解集中,把已经得到的答案再次加入。
即使用数组标记数组中每个数,使得在一次排列中里面的一个元素只能用一次。- Leetcode 0046全排列
- Leetcode 0047 全排列||(数组中含有重复的数)
排列问题的循环一般是重0开始,而不是startIndex开始
排列问题一般都涉及标记数组,去重
-
棋盘问题
用树的方式,表示棋盘问题- Leetcode 51.N皇后问题
- Leetcode 37 解数独
将棋盘按行循环
判断每个位置的合法性
-
其他问题
- Leetcode 332.重新安排行程
利用哈希表实现回溯
- Leetcode 332.重新安排行程
总结
1,如何理解回溯法的搜索过程?
回溯的过程可以理解为for循环+递归,循环每一个解集,递归求出这个解集的答案
2,什么时候用startIndex,什么时候不用?
允许答案中有重复元素的不使用startIndex,每次循环从0开始。例如 组合问题(39),排列问题
反之,在子集,组合77等问题要求解集中不含有重复元素时要使用startIndex标记每次循环开始位置。
3,如何去重?怎么去重?
当数组中存在重复元素,或者对排序有要求时就要考虑去重。当然去重还要考虑不同子集间去重,子集内不同元素去重,还是两者都存在。
常见的方式:数组标记,哈希表
4.如何理解二维递归?
可以把去重问题理解为二维递归
参考: