哪些题目可用用深搜或广搜?
图或树的遍历
BFS
实现 BFS 时需要考虑以下问题:
- 队列:用来存储每一轮遍历得到的节点;
- 标记:对于遍历过的节点,应该将它标记,防止重复遍历。
void BFS()
{
定义队列;
定义备忘录,用于记录已经访问的位置;
判断边界条件,是否能直接返回结果的。
将起始位置加入到队列中,同时更新备忘录。
while (队列不为空) {
获取当前队列中的元素个数。
for (元素个数) {
取出队头节点。
判断是否到达终点。
获取它对应的下一个所有的节点,过滤不符合条件的位置。
新位置插入队尾。
更新备忘录。
}
}
}
DFS
实现 DFS 时需要考虑以下问题:
- 栈:用栈来保存当前节点信息,当遍历新节点返回时能够继续遍历当前节点。可以使用递归栈。
- 标记:和 BFS 一样同样需要对已经遍历过的节点进行标记。
Backtracking
Backtracking(回溯)属于 DFS。
- 普通 DFS 主要用在可达性问题 ,这种问题只需要执行到特点的位置然后返回即可。
- 而 Backtracking 主要用于求解排列组合问题,例如有 { 'a','b','c' } 三个字符,求解所有由这三个字符排列得到的字符串,这种问题在执行到特定的位置返回之后还会继续执行求解过程。
因为 Backtracking 不是立即返回,而要继续求解,因此在程序实现时,需要注意对元素的标记问题:
- 在访问一个新元素进入新的递归调用时,需要将新元素标记为已经访问,这样才能在继续递归调用时不用重复访问该元素;
- 但是在递归返回时,需要将元素标记为未访问,因为只需要保证在一个递归链中不同时访问一个元素,可以访问已经访问过但是不在当前递归链中的元素。
1091. 二进制矩阵中的最短路径
还不会写 BFS 代码,参考了题解
测试用例:
- [[0]]
- 起始点即为阻塞路:[[1, 0, 0], [1, 1, 0], [1, 1, 0]]
注意点:
-
遍历各个方向的先后顺序影响运行速度。一般是先右再左
int[][] direction = {{1, 0}, {1, 1}, {1, -1}, {0, 1}, {0, -1}, {-1, 0}, {-1, -1}, {-1, 1}};
-
⭐标记位置的选择很重要。某个点插入队列后应立即标为 1,如下代码:
否则,搜索到某层时,会把同一层的兄弟节点作为下一层节点。如下图,遍历到第二层,红框内的两个都会把对方作为下一层节点放入队列。
因此,可能会出现这种无意义的路径,导致队列太大,运行时堆内存溢出
279. 完全平方数
BFS
如果不是放在“搜索”专题里,很难想到 BFS
最好不要用 sqrt()
(每个循环里都使用这个方法,速度很慢),而是一次性生成小于等于 的平方数(每个循环里仅需判断大小)。
数学定理
- 任何正整数都可以拆分成不超过 4 个数的平方和 --> 答案只可能是 1, 2, 3, 4
- 如果一个数最少可以拆成4个数的平方和,则这个数还满足
--> 因此可以先看这个数是否满足上述公式,如果不满足,答案就是 1, 2, 3 了
- 如果这个数本来就是某个数的平方,那么答案就是 1,否则答案就只剩 2, 3 了
- 如果答案是 2,即
,那么我们可以枚举
,来验证,如果验证通过则答案是 2
- 只能是 3
动态规划
动态规划 markdown 中有
127. 单词接龙
-
如何对
List
中已加入队列的单词做标记?
直接删除即可Iterator<String> it = wordList.iterator(); while (it.hasNext()) { String word = it.next(); if (...) { it.remove(); } }
-
判断字符串是否相等时,一开始使用了
==
,并在 Intellij IDEA 和力扣的 Playground 里测试通过,但在力扣的控制台无法通过。应当使用equals()
。- 基类
Object
类具有equals()
方法,其初始行为是地址比较,但在一些类如String, Integer, Date
中被重写为值比较。 - 八大数据类型中数据变量中直接存储值,所以
==
为值比较。而在引用类型中,==
为地址比较。
- 基类
双端BFS
695. 岛屿的最大面积
递归或迭代实现DFS都可,代码还不太熟练,参考了题解
⭐547. 朋友圈
以邻接矩阵表示朋友关系,即 的对称矩阵。与上题代码写法略不相同。代码还不太熟练。
DFS & BFS & 并查集
官方题解
并查集解法看代码和注释
417. 太平洋大西洋水流问题
注意审题,“太平洋”和“大西洋”,并不在一个点上,如下图:
~
代表太平洋海域;*
代表大西洋海域。
因此左和上边都可以直接抵达太平洋,同理右和下边也可直接抵达大西洋。所以,从左和上边上的点出发,判断其余点能否到达太平洋(逆流判断);从右和下边上的点出发,判断其余点能否到达大西洋
17. 电话号码的字母组合
回溯
- 如何确认已经进行到第几个数字键? - 根据
prefix
的长度确认。 - 需要考虑如何返回最后的结果。
不熟练
⭐93. 复原IP地址
暴力解法
自用了回溯,思路是比较简单的,但是对一些条件的判断想了挺久。
比如:对于 1111
进行到 111.1
后如何停止; 对于 2552552552
进行到 25.52.55.25
后如何不输出
❓79. 单词搜索
力扣控制台结果输出正确,但提交时是错的,没发现为啥。
257. 二叉树的所有路径
注意点:
Java 的引用类型都是引用传递。StringBuilder
的字符串拼接、删除等操作是原地进行的,因此深一层的递归对 StringBuilder
类型的字符串进行修改后,退出递归时要还原。
而 String
类是不可变的,因此 String
的拼接操作需要赋给一个指向新的内存地址的变量。如 path = path + "->"
或 path = path.concat("->")
。因此,传入下一层的字符串已经指向不同的地址,退出递归时不用还原。
所以下方左子树递归返回后,要使 path
变为递归之前的内容
private static void dfs(List<String> pathList, StringBuilder path, TreeNode root) {
...
int pathLen = path.length();
dfs(pathList, path, root.left);
path.delete(pathLen, path.length());
...
}
46. 全排列 I & II
I 给定序列不包含重复数字
插入法生成全排列:(组合数学)
⭐II 可包含重复数字
同样也用插入法,但是插入方式为
①〇〇 -> ①②〇 -> ①②③
①〇〇 -> ①③〇 -> ①③②
①〇〇 -> ...
判重:在添加一个元素时,判断这个元素是否等于前一个元素,如果等于,并且前一个元素还未访问,那么就跳过这个元素。
当前元素与前一个元素相等(设值为 a
),且前一个元素还未访问:因为退出递归时 visited[i] = false
。所以这个判定条件表示,在这个位置上,以数值 a
开头的排列前一个元素(也是 a
)已经排过了,所以跳过。
77. 组合 && 组合总和 I && 组合总和 II && 组合总和 III
回溯 + 剪枝
见代码及注释。“组合总和 II”的去重思路和上一题“全排列 II”相似
78. 子集
压缩序列出子集:

截图来自组合数学。压缩序要对子集反复读取,较费时间。
⭐二进制方法:
leetcode-cn.com/problems/su…

二进制位数代表数字,比如:0010 代表
[2]
,0011 代表 [1, 2]
37. 解数独
解数独的难点在于如何判定只出现一次这个条件
private static boolean[][] rowsUsed = new boolean[9][10];
private static boolean[][] colsUsed = new boolean[9][10];
private static boolean[][] cubesUsed = new boolean[9][10];
rowsUsed[i][j]
表示第 行是否已填好数字
又出现控制台正确,但力扣过不了的情况
51. N皇后
N皇后的难点在于斜线上是否已存在皇后
自己的思路:遍历左上和右上两个方向,判断是否已存在皇后
更好的方法: 的矩阵中,正副对角线各有
条,若某点摆上皇后,则把这个点所属的正副对角线设为已使用
转换关系如下:
topLeftIndex = column + n - 1 - row
topRightIndex = column + row