近期本人在leetcode上刷了不少回溯的题目,leetcode回溯专题能刷的题都刷了,现在对回溯做一个简单的总结。
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。
不把它的定义拿过来总觉得此文缺点什么,所以我从百度百科回溯算法摘抄了上面这段文字。通俗来讲,回溯是一种尝试多种可能的搜索过程,当满足条件时一直走下去,当不满足时回过头来尝试其他路径,不撞南墙不回头。
什么时候使用回溯?
当一个问题需要多种可能的结果时,并且此时即使暴力编写也无从下手,请试试回溯,例如排列组合问题。
回溯代码框架
回溯算法是有框架的,首先它是一个递归,需要设置递归终止条件;递归函数中找出下一步所有可能路径,然后对每个可能递归调用,调用前设置,调用后清除设置。
大致如下:
void backtrace() {
if (终止条件) {
处理结果
return;
}
for (下一步所有可能) {
剪枝
设置变量
backtrace(); // 递归
清除变量设置 // 回溯
}
}
当下一步所有可能只有1种时,此时就是纯粹的递归,没的选择一条路走下去就是,此时用不着回溯。
剪枝是为了提升效率,明知不可能就不要做了,比如排列中数字1已经使用了,下一步就不要再使用了。在列举所有可能的过程中考虑剪枝条件也是可以的。
回溯能够解决哪类问题?
排列:
46. 全排列:无重复数字全排列
47. 全排列 II:有重复数字全排列
剑指 Offer 38. 字符串的排列 & 面试题 08.08. 有重复字符串的排列组合:有重复字符全排列
1079. 活字印刷:重复字符串中任意字符的所有排列
面试题 08.09. 括号 & 22. 括号生成:打印n对括号的所有合法组合
组合:
77. 组合:返回 1 ... n 中所有可能的 k 个数的组合
216. 组合总和 III:找出所有相加之和为 n 的 k 个数的组合
39. 组合总和:和为target的数的组合(数可重复使用)
40. 组合总和 II:和为target的数的组合(数不可重复使用)
17. 电话号码的字母组合:数字2-9对应3-4个字母
1780. 判断一个数字是否可以表示成三的幂的和:k个数的组合
子集、分割:
131. 分割回文串:分割成回文串
842. 将数组拆分成斐波那契序列:将数字字符串拆分成斐波那契序列
93. 复原 IP 地址:将数字字符串复原成IP地址
78. 子集 & 面试题 08.04. 幂集:输出所有子集
306. 累加数:与斐波那契类似