递归回溯
1. 概念
站在回溯树的一个节点上,你只需要思考 3 个问题:
1、路径:也就是已经做出的选择。
2、选择列表:也就是你当前可以做的选择。
3、结束条件:也就是到达决策树底层,无法再做选择的条件
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
典型的 DFS 但是没有回溯的题目:17. 电话号码的字母组合
2. 解题技巧(我的总结)
1> 使用哈希表存储答案,使用mark标记已访问的防止重复搜索,状态较少(<32)时用一个int即可
题目 | 说明 | 实现 |
---|---|---|
464. 我能赢吗 | 使用int记录已选数字 | 我的提交 |
1387. 将整数按权重排序 | 使用int记录已计算过的 | 我的提交 |
1361. 验证二叉树 | 记录每个节点为根时其下的节点数目,直到等于n | 我的提交 |
2745. 构造最长的新字符串 | 只要知道每个串剩余数量和前一个串类型,就能唯一确定最大长度,故采用状态压缩dfs | 我的提交 |
2> 岛屿问题:曼哈顿距离,广度优先搜索,某一性质(边界)扩散,通过更改标记防止重复搜索
题目 | 说明 | 实现 |
---|---|---|
934. 最短的桥 | 两岛屿使用不同的标记,bfs扩散岛屿1的面积直至和岛屿2相遇 | 我的提交 |
1020. 飞地的数量 | 从边界扩散,剩余的是有效的1 | 我的提交 |
1034. 边界着色 | dfs,是边界标记为-1,不是标记为0,(不是边界 :4个方向的val=target/0/-1的数量==4) | 我的提交 |
1254. 统计封闭岛屿的数目 | 边缘的0开始扩散并标志为1,接着深度优先搜索所有独立岛屿 | 我的提交 |
1765. 地图中的最高点 | 预处理所有的格子标记为-1,大于等于0代表格子被访问了,从睡眠水面扩散高度,每轮增加1 | 我的提交 |
2658. 网格图中鱼的最大数目 | sum(x,y) 递归找到所有相邻的水域,每次访问某一水域后都将其至为0防止回头 | 我的提交 |
3> 二叉树技巧
只考虑根节点 (先考虑根节点为nil,再考虑和根节点的关系,递归到下一层)
使用map记录父节点
题目 | 说明 | 实现 |
---|---|---|
669. 修剪二叉搜索树 | 根节点不在区间内/在区间内两种情况 | 我的提交 |
623. 在二叉树中增加一行 | 根节点在新层 前两层以上/前一层/新层 三种情况 | 我的提交 |
863. 二叉树中所有距离为 K 的结点 | 记录父节点,考虑下面的后再向上考虑每个父节点 | 我的提交 |
971. 翻转二叉树以匹配先序遍历 | 预处理统计每个节点下的节点数目,从而对数组划分 | 我的提交 |
652. 寻找重复的子树 | 二叉树的序列化 | 我的提交 |
1443. 收集树上所有苹果的最少时间 | 当前节点time = sum(每个子节点time+2) | 我的提交 |
124. 二叉树中的最大路径和 | 递归每个根节点往下的最长路径长度,过程记录每个根节点+左右子树的最大长度 | 我的提交 |
2925. 在树上执行操作以后得到的最大分数 | 要么只保留根节点 要么拿走根同时保证所有孩子递归满足题意 | 我的提交 |
面试题 04.05. 合法二叉搜索树 | 记录三个值:是否二叉搜索树、最小值、最大值,输入必须是非nil的,防止极端数据 | 我的提交 |
4> 棋盘问题,三步:
递归终止,存储答案
遍历当前位置所有合法解,进入下一位置
当前位置无合法解,回退,撤销一切更改
题目 | 说明 | 实现 |
---|---|---|
51. N 皇后 | 从上往下为每一行选择Q位置 | 我的提交 |
1391. 检查网格中是否存在有效路径 | 使用4维特征记录每种街道的上下左右可行性 | 我的提交 |
1905. 统计子岛屿 | ps (每访问一个节点就更改其值以去重) | 我的提交 |
5> 降低搜索空间,从排序、忽略顺序、只考虑奇偶性、对实体分类等方面简化
题目 | 说明 | 实现 |
---|---|---|
672. 灯泡开关 Ⅱ | 只考虑每组灯泡被按了奇偶次,灯泡可以分成4组 | 我的提交 |
473. 火柴拼正方形 | 降序排列降低后面的枚举次数 | 我的提交 |
6> 深度优先套路
dfs(env,pos,mark):
if mark[pos] return
else{
所有合法的下一位置nextPos:
处理环境
dfs(env,nextPos,mark)
恢复环境
}
题目 | 说明 | 实现 |
---|---|---|
841. 钥匙和房间 | 深度优先每个房间 | 我的提交 |
7> 预处理条件,使用map形式方便查找
下一合法位置
题目 | 说明 | 实现 |
---|---|---|
851. 喧闹和富有 | map记录比每个用户有钱的直接用户 | 我的提交 |
690. 员工的重要性 | map记录比每个id对应的实体 | 我的提交 |
753. 破解保险箱 | map记录比每个可能的钥匙,通过每次在字符串后面添加一位,搜索直至所有key都出现 | 我的提交 |
756. 金字塔转换矩阵 | map记录每个二元组上面能放的合法字符 | 我的提交 |
1042. 不邻接植花 | map记录比每个花园的邻居 | 我的提交 |
2368. 受限条件下可到达节点的数目 | nexts记录每个节点邻居,visited记录受限/已访问节点 | 我的提交 |
8> 利用二叉树性质,转换、简化题目问题为可以递归的问题
题目 | 说明 | 实现 |
---|---|---|
1530. 好叶子节点对的数量 | 题意->求解所有树枝节点的合法路径->递归记录每个节点到旗下叶子的路径长度 | 我的提交 |
1559. 二维网格图中探测环 | dfs中两个分支之间不会产生相连->dfs找环 | 我的提交 |
652. 寻找重复的子树 | 二叉树的序列化 | 我的提交 |
2385. 感染二叉树需要的总时间 | 二叉树一个节点将树分成三个部分互不相连 -> (dfs 邻居时间的最大值) + 1 | 我的提交 |
3. 更多练习
3.1 子集型回溯
题目 | 说明 | 题解 |
---|---|---|
78. 子集 | 和 LC.39 类似,按照 begin 为起点遍历回溯就可以 | 通过 |
90. 子集 II | 在 LC.78 的基础上有重复元素的考虑,和 LC.40 类似的剪枝 🔥 | 通过 |
131. 分割回文串 | 枚举字符之间的逗号,按照 idx 顺序回溯,判断回文 | 通过 |
698. 划分为k个相等的子集 | 抽象成 k 个桶,每个桶的容量为子集和大小 | 通过 |
473. 火柴拼正方形 | 和 LC.698 一模一样,抽象成 4 个桶 | 通过 |
2305. 公平分发饼干 | k 个桶,但是桶大小未知,从大到小DFS回溯 | 通过 |
854. 相似度为 K 的字符串 | DFS 回溯,剪枝,有点难… | 通过 |
1255. 得分最高的单词集合 | 参考灵神的子集型回溯,选或不选的思想,注意恢复现场 🔥 | 0x3F |
3.2 组合型回溯
题目 | 说明 | 题解 |
---|---|---|
39. 组合总和 | 组合问题需要按照某种顺序搜索:每一次搜索的时候设置 下一轮搜索的起点 begin ,也可以排序之后加速剪枝 | 通过 |
40. 组合总和 II | 和 LC.39 区别是这个有重复元素,需要去掉当前层(for循环中)第二个数值重复的节点 🔥 | 剪枝 |
77. 组合 | 和 LC.39 类似,按照 begin 为起点遍历然后回溯就可以,剪枝:剩余元素个数需要多余 k | 通过 |
216. 组合总和 III | 和 LC.40 类似,这题没有重复元素,两个剪枝:小于最小的 || 大于最大的 🔥 | 0x3F |
22. 括号生成 | 直接 DFS 思路(选或不选)更简单,比较剩余左右括号数量剪枝 | LWW |
3.3 排列型回溯
题目 | 说明 | 题解 |
---|---|---|
46. 全排列 | 回溯入门问题,无重复元素的排列问题 | 通过 |
47. 全排列 II | 去重是关键,排序比较,上一个相同的元素撤销之后再剪枝 🔥 | 剪枝图 |
51. N 皇后 | 第 row 行填入 path[row] 这个数,求 path 满足条件的全排列 | Carl |