# 回溯
搜索是把所有可能按照一定的顺序、规则去试探一遍, 从而求出问题的解。
回溯搜索是深度优先搜索(DFS)的一种,主要的区别是:回溯法在求解过程中不保留完整的树结构,而深度优先搜索则记下完整的搜索树。即,回溯法可以被认为是一个有过剪枝的DFS过程。
# 适用情况
序列路径
最优所有可能
# 需要思考 3 个问题:
1、选择路径:已经做出的选择经过的路径。
2、选择列表:当前可以继续选择候选节点(or剪枝)。
3、结束条件:到达决策树底层,无法再做选择的条件。
# 伪代码
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择(进入递归节点前的操作)
backtrack(路径, 选择列表)
撤销选择(离开递归节点后的操作)
# 相关题目
77. 组合
给定两个整数 n和 k,返回 1 ... n中所有可能的 k个数的组合。 corner: k==0 vs n<k 路径:无序 选择:因为无序,所以下一层的候选一定不会重复,可以认为是按序递增的。 结束:深度
# 两行代码:递归+数学C(n,k)=C(n-1,k-1)+C(n-1,k).
# corner case
without_last = self.combine(n-1,k)
with_last = [[n] + combo for combo in self.combine(n-1, k-1)]
# return with_last + without_list
39. 组合总和
无重复元素的数组candidates
和一个目标数target
,找出candidates
中所有可以使数字和为target
的组合。 corner: not candidates or target == 0 路径:无序 选择:不大于target 结束:等于target、等于数组长度
# 可以用target减去已有路径和,方便写代码
# 剪枝 if candidates[idx] > target: break
40. 组合总和 II
candidates
中的每个数字在每个组合中只能使用一次。
corner: not candidates or target == 0
路径:无序
选择:不大于target
结束:等于target、等于数组长度
# 重复可以先排序 方便剪枝
# 剪枝
if candidates[idx] > target: break
if candidates[idx-1] == candidates[idx]:continue
46. 全排列
给定一个 没有重复数字的序列,返回其所有可能的全排列。 corner: 路径: 选择: 结束:等于数组长度
# 剪枝 if visited[idx] == 1: continue
# 节约空间: 可以通过交换完成访问标记
# nums[i], nums[index] = nums[index], nums[i]
# backtrack
# nums[i], nums[index] = nums[index], nums[i]
# 库函数 list(itertools.permutations(nums))
47. 全排列 II
给定一个 有重复数字的序列,返回其所有可能的全排列。 corner: 路径: 选择: 结束:
78. 子集
corner: 路径: 选择: 结束:
90. 子集 II
corner: 路径: 选择: 结束:
22. 括号生成
corner: 路径: 选择: 结束:
# 注意:
- 举出边界数据,并画出树,有利于写清代码。
- 写递归的时候,要先写递归终止条件。
- 注意递归深度和每一层需要完成什么工作。
- 分析剪枝条件,比如重复就可以优先通过排序解决。
# 参考
leetcode-题解
深度优先搜索和回溯