文章目录
解题技巧
深度优先搜索(DFS,Depth-First Search)和广度优先搜索(BFS,Breadth-First Search)都是用来遍历或搜索树或图的算法。它们的主要区别体现在搜索顺序和使用的数据结构上。
搜索顺序:
- DFS:深度优先搜索会尽可能深地搜索树的分支,直到到达给定分支的末端,然后再回溯继续下一分支。换句话说,DFS优先沿着某一支路线深入,直到不能再深入为止,然后再按路径回溯。
- BFS:广度优先搜索则是从根节点开始,先访问其所有相邻的节点,然后再按照访问顺序访问这些节点的子节点。简而言之,BFS是按层次依次访问的。
使用的数据结构:
- DFS:通常使用堆栈实现。在递归实现中,系统的调用栈本身可以作为堆栈。
- BFS:通常使用队列实现,将相邻节点逐个加入队列中,然后逐个访问。
空间复杂度:
- DFS:空间复杂度与深度有关,最坏情况下可能需要存储所有节点,因此是O(|V|),其中V是节点数量。
- BFS:在最坏的情况下,整个图的所有节点都可能同时在队列中,因此空间复杂度也是O(|V|)。
时间复杂度:
- 两者的时间复杂度在无权图中通常相同,都是O(|V| + |E|),其中V是顶点数量,E是边的数量。
是否找到最短路径:
- DFS:不一定能找到最短路径。
- BFS:对于无权图,总是能找到最短路径。
适用场景:
- DFS:适用于目标明确,需要深入探索的场景,或者需要找到所有可能解的问题。
- BFS:适用于最短路径问题或者问题的解在浅层次上。
其他特性:
- DFS:可能导致路径很深时探索耗时,且可能陷入死循环(在非树结构中)。
- BFS:在内存限制严格的场合下可能不适用,因为在存储宽度较大的层时可能消耗大量内存。
DFS的代码框架:
void dfs(参数) { if (终止条件) { 存放结果; return; } for (选择:本节点所连接的其他节点) { 处理节点; dfs(图,选择的节点); // 递归 回溯,撤销处理结果 } }
BFS:
使用队列来存储节点
797.所有可能的路径
给你一个有
n
个节点的 有向无环图(DAG),请你找出所有从节点0
到节点n-1
的路径并输出(不要求按特定顺序)
graph[i]
是一个从节点i
可以访问的所有节点的列表(即从节点i
到节点graph[i][j]
存在一条有向边)。示例 1:
![]()
输入:graph = [[1,2],[3],[3],[]] 输出:[[0,1,3],[0,2,3]] 解释:有两条路径 0 -> 1 -> 3 和 0 -> 2 -> 3
示例 2:
![]()
输入:graph = [[4,3,1],[3,2,4],[3],[4],[]] 输出:[[0,4],[0,3,4],[0,1,3,4],[0,1,2,3,4],[0,1,4]]
提示:
n == graph.length
2 <= n <= 15
0 <= graph[i][j] < n
graph[i][j] != i
(即不存在自环)graph[i]
中的所有元素 互不相同- 保证输入为 有向无环图(DAG)
DFS-回溯
- 时间复杂度: O ( n × 2 n ) O(n × 2^n) O(n×2n),n为图中点的数量。
- 空间复杂度: O ( n ) O(n) O(n)。
function allPathsSourceTarget(graph: number[][]): number[][] {
const res: number[][] = [];
const path: number[] = [];
const dfs = (x: number) => {
if (x === graph.length - 1) { // 终止条件,题目要求 0 到 n-1 的节点路径
res.push(path.slice());
return;
}
for (let i = 0; i < graph[x].length; i++) {
path.push(graph[x][i]);
dfs(graph[x][i])
path.pop();
}
}
path.push(0);
dfs(0);
return res;
};
200.岛屿数量
给你一个由
'1'
(陆地)和'0'
(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入:grid = [ ["1","1","1","1","0"], ["1","1","0","1","0"], ["1","1","0","0","0"], ["0","0","0","0","0"] ] 输出:1
示例 2:
输入:grid = [ ["1","1","0","0","0"], ["1","1","0","0","0"], ["0","0","1","0","0"], ["0","0","0","1","1"] ] 输出:3
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 300
grid[i][j]
的值为'0'
或'1'
DFS
解题思路:
-
遍历网格:遍历整个二维网格,一旦找到一个’1’,就从那里开始执行深度优先搜索,并将岛屿数量加1。
-
深度优先搜索:在深度优先搜索中,我们会访问当前’1’所连接的所有’1’,并将它们标记为已访问(例如,可以将它们标记为’2’或者使用另一个同样大小的二维数组来跟踪访问状态)。
a. 递归调用:对于当前’1’,我们将查看它上下左右四个方向的邻居,并递归地对每一个未访问过的’1’邻居进行深度优先搜索。
b. 边界条件:在查看邻居时,要确保不会走出网格的边界,并且仅在找到’1’时才递归。
c. 标记访问:在访问一个’1’之后,要立即将其标记为已访问,以避免重复访问。
-
返回岛屿数量:在遍历完整个网格并进行了深度优先搜索之后,返回岛屿数量。

- 时间复杂度: O ( m ∗ n ) O(m*n) O(m∗n),m和n分别为行数和列数
- 空间复杂度: O ( m ∗ n ) O(m*n) O(m∗n)
function numIslands(grid: string[][]): number {
const dfs = (i: number, j: number) => {
if (i < 0 || i >= grid.length || j < 0 || j >= grid[0].length) return; // 超出边界
if (grid[i][j] === '0' || grid[i][j] === '2') return;
grid[i][j] = '2'
dfs(i - 1, j);
dfs(i + 1, j);
dfs(i, j - 1);
dfs(i, j + 1);
}
let count = 0;
for (let i = 0; i < grid.length; i++) {
for (let j = 0; j < grid[i].length; j++) {
if (grid[i][j] === '1') {
dfs(i, j);
count++;
}
}
}
return count;
};
BFS
解题思路:
广度优先搜索:
a. 出队列并访问:从队列中取出一个坐标,并检查其上下左右四个方向的邻居。
b. 检查邻居:对于每个未访问过的’1’邻居,将其坐标添加到队列中,并将其标记为已访问(可以通过将其值改为’2’来实现)。
c. 继续迭代:重复上述过程,直到队列为空。此时,一个岛屿的所有部分都已被访问。
- 时间复杂度: O ( m ∗ n ) O(m*n) O(m∗n),m和n分别为行数和列数
- 空间复杂度: O ( m i n ( ( m ∗ n ) ) ) O(min((m*n))) O(min((m∗n)))
function numIslands(grid: string[][]): number {
const bfs = (i: number, j: number) => {
const queue: number[][] = [];
queue.push([i, j]);
while (queue.length) {
const [r, c] = queue.shift();
if (r < 0 || r >= grid.length || c < 0 || c >= grid[0].length) continue;
if (grid[r][c] === '0' || grid[r][c] === '2') continue;
grid[r][c] = '2';
queue.push([r + 1, c]);
queue.push([r - 1, c]);
queue.push([r, c + 1]);
queue.push([r, c - 1]);
}
}
let count = 0;
for (let i = 0; i < grid.length; i++) {
for (let j = 0; j < grid[i].length; j++) {
if (grid[i][j] === '1') {
bfs(i, j);
count++
}
}
}
return count;
};
695.岛屿的最大面积
给你一个大小为
m x n
的二进制矩阵grid
。岛屿 是由一些相邻的
1
(代表土地) 构成的组合,这里的「相邻」要求两个1
必须在 水平或者竖直的四个方向上 相邻。你可以假设grid
的四个边缘都被0
(代表水)包围着。岛屿的面积是岛上值为
1
的单元格的数目。计算并返回
grid
中最大的岛屿面积。如果没有岛屿,则返回面积为0
。示例 1:
![]()
输入:grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,1,1,0,1,0,0,0,0,0,0,0,0],[0,1,0,0,1,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,0,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,0,0,0,0,0,0,1,1,0,0,0,0]] 输出:6 解释:答案不应该是 11 ,因为岛屿只能包含水平或垂直这四个方向上的 1 。
示例 2:
输入:grid = [[0,0,0,0,0,0,0,0]] 输出:0
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 50
grid[i][j]
为0
或1
DFS
- 时间复杂度: O ( m ∗ n ) O(m*n) O(m∗n),m和n分别为行数和列数
- 空间复杂度: O ( m ∗ n ) O(m*n) O(m∗n)
function maxAreaOfIsland(grid: number[][]): number {
const dfs = (i: number, j: number) => {
if (i < 0 || i >= grid.length || j < 0 || j >= grid[0].length) return 0;
if (grid[i][j] === 0 || grid[i][j] === 2) return 0;
grid[i][j] = 2;
let area = 1;
area += dfs(i + 1, j);
area += dfs(i - 1, j)
area += dfs(i, j + 1);
area += dfs(i, j - 1);
return area;
}
let maxArea = 0;
for (let i = 0; i < grid.length; i++) {
for (let j = 0; j < grid[i].length; j++) {
if (grid[i][j] === 1) {
maxArea = Math.max(maxArea, dfs(i, j))
}
}
}
return maxArea;
};
BFS
- 时间复杂度: O ( m ∗ n ) O(m*n) O(m∗n),m和n分别为行数和列数
- 空间复杂度: O ( m i n ( ( m ∗ n ) ) ) O(min((m*n))) O(min((m∗n)))
function maxAreaOfIsland(grid: number[][]): number {
const bfs = (i: number, j: number) => {
const queue: number[][] = [];
queue.push([i, j]);
let area = 0
while (queue.length) {
const [r, c] = queue.shift();
if (r < 0 || r >= grid.length || c < 0 || c >= grid[0].length) continue;
if (grid[r][c] === 0 || grid[r][c] === 2) continue;
area++;
grid[r][c] = 2;
queue.push([r + 1, c]);
queue.push([r - 1, c]);
queue.push([r, c + 1]);
queue.push([r, c - 1]);
}
return area;
}
let maxArea = 0;
for (let i = 0; i < grid.length; i++) {
for (let j = 0; j < grid[i].length; j++) {
if (grid[i][j] === 1) {
maxArea = Math.max(maxArea, bfs(i, j));
}
}
}
return maxArea;
};
1020.飞地的数量
给你一个大小为
m x n
的二进制矩阵grid
,其中0
表示一个海洋单元格、1
表示一个陆地单元格。一次 移动 是指从一个陆地单元格走到另一个相邻(上、下、左、右)的陆地单元格或跨过
grid
的边界。返回网格中 无法 在任意次数的移动中离开网格边界的陆地单元格的数量。
示例 1:
![]()
输入:grid = [[0,0,0,0],[1,0,1,0],[0,1,1,0],[0,0,0,0]] 输出:3 解释:有三个 1 被 0 包围。一个 1 没有被包围,因为它在边界上。
示例 2:
![]()
输入:grid = [[0,1,1,0],[0,0,1,0],[0,0,1,0],[0,0,0,0]] 输出:0 解释:所有 1 都在边界上或可以到达边界。
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 500
grid[i][j]
的值为0
或1
DFS
- 时间复杂度: O ( m ∗ n ) O(m*n) O(m∗n),m和n分别为行数和列数
- 空间复杂度: O ( m ∗ n ) O(m*n) O(m∗n)
function numEnclaves(grid: number[][]): number {
const dfs = (i: number, j: number) => {
if (i < 0 || i >= grid.length || j < 0 || j >= grid[0].length) return Infinity;
if (grid[i][j] === 0 || grid[i][j] === 2) return 0;
grid[i][j] = 2;
let count = 1;
count += dfs(i + 1, j);
count += dfs(i - 1, j);
count += dfs(i, j + 1);
count += dfs(i, j - 1);
return count;
}
let count = 0;
for (let i = 0; i < grid.length; i++) {
for (let j = 0; j < grid[i].length; j++) {
if (grid[i][j] === 1) {
const curCount = dfs(i, j);
if (curCount !== Infinity) {
count += curCount;
}
}
}
}
return count;
};
BFS
- 时间复杂度: O ( m ∗ n ) O(m*n) O(m∗n),m和n分别为行数和列数
- 空间复杂度: O ( m i n ( ( m ∗ n ) ) ) O(min((m*n))) O(min((m∗n)))
function numEnclaves(grid: number[][]): number {
const bfs = (i: number, j: number) => {
const queue: number[][] = [];
queue.push([i, j]);
let count = 0;
let isEdge = false
while (queue.length) {
const [r, c] = queue.shift();
if (r < 0 || r >= grid.length || c < 0 || c >= grid[0].length) {
isEdge = true;
continue;
}
if (grid[r][c] === 0 || grid[r][c] === 2) continue;
grid[r][c] = 2;
count++;
queue.push([r + 1, c]);
queue.push([r - 1, c]);
queue.push([r, c + 1]);
queue.push([r, c - 1]);
}
return isEdge ? 0 : count;
}
let ans = 0;
for (let i = 0; i < grid.length; i++) {
for (let j = 0; j < grid[i].length; j++) {
if (grid[i][j] === 1) {
ans += bfs(i, j);
}
}
}
return ans;
};
130.被围绕的区域
给你一个
m x n
的矩阵board
,由若干字符'X'
和'O'
,找到所有被'X'
围绕的区域,并将这些区域里所有的'O'
用'X'
填充。示例 1:
![]()
输入:board = [["X","X","X","X"],["X","O","O","X"],["X","X","O","X"],["X","O","X","X"]] 输出:[["X","X","X","X"],["X","X","X","X"],["X","X","X","X"],["X","O","X","X"]] 解释:被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。 任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都会被填充为 'X'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。
示例 2:
输入:board = [["X"]] 输出:[["X"]]
提示:
m == board.length
n == board[i].length
1 <= m, n <= 200
board[i][j]
为'X'
或'O'
DFS
解题思路:
从边缘开始,将边缘相连的全部置为#
,之后再遍历一次,遇到O
置为X
,遇到#
置为O
。
- 时间复杂度: O ( m ∗ n ) O(m*n) O(m∗n),m和n分别为行数和列数
- 空间复杂度: O ( m ∗ n ) O(m*n) O(m∗n)
/**
Do not return anything, modify board in-place instead.
*/
function solve(board: string[][]): void {
const dfs = (i: number, j: number) => {
if (i < 0 || i >= board.length || j < 0 || j >= board[0].length) return;
if (board[i][j] === '#' || board[i][j] === 'X') return;
board[i][j] = '#';
dfs(i + 1, j);
dfs(i - 1, j);
dfs(i, j + 1);
dfs(i, j - 1);
}
for (let i = 0; i < board.length; i++) {
for (let j = 0; j < board[i].length; j++) {
const isEdge = i === 0 || j === 0 || i === board.length - 1 || j === board[i].length - 1
if (isEdge && board[i][j] === 'O') {
dfs(i, j);
}
}
}
for (let i = 0; i < board.length; i++) {
for (let j = 0; j < board[i].length; j++) {
if (board[i][j] === '#') {
board[i][j] = 'O'
} else if (board[i][j] === 'O') {
board[i][j] = 'X'
}
}
}
};
417.太平洋大西洋水流问题
有一个
m × n
的矩形岛屿,与 太平洋 和 大西洋 相邻。 “太平洋” 处于大陆的左边界和上边界,而 “大西洋” 处于大陆的右边界和下边界。这个岛被分割成一个由若干方形单元格组成的网格。给定一个
m x n
的整数矩阵heights
,heights[r][c]
表示坐标(r, c)
上单元格 高于海平面的高度 。岛上雨水较多,如果相邻单元格的高度 小于或等于 当前单元格的高度,雨水可以直接向北、南、东、西流向相邻单元格。水可以从海洋附近的任何单元格流入海洋。
返回网格坐标
result
的 2D 列表 ,其中result[i] = [ri, ci]
表示雨水从单元格(ri, ci)
流动 既可流向太平洋也可流向大西洋 。示例 1:
![]()
输入: heights = [[1,2,2,3,5],[3,2,3,4,4],[2,4,5,3,1],[6,7,1,4,5],[5,1,1,2,4]] 输出: [[0,4],[1,3],[1,4],[2,2],[3,0],[3,1],[4,0]]
示例 2:
输入: heights = [[2,1],[1,2]] 输出: [[0,0],[0,1],[1,0],[1,1]]
提示:
m == heights.length
n == heights[r].length
1 <= m, n <= 200
0 <= heights[r][c] <= 105
DFS
解题思路:
水往高处流,计算两个洋所能到达的点,重叠的点就是最终的结果。



- 时间复杂度: O ( m ∗ n ) O(m*n) O(m∗n)
- 空间复杂度: O ( m ∗ n ) O(m*n) O(m∗n)
function pacificAtlantic(M: number[][]): number[][] {
const n = M.length;
const m = M[0].length;
const P = Array.from({ length: n }, () => Array(m).fill(0));
const A = Array.from({ length: n }, () => Array(m).fill(0));
const ans: number[][] = [];
function dfs(
visited: number[][],
i: number,
j: number,
): void {
if (visited[i][j]) return;
visited[i][j] = 1;
if (A[i][j] && P[i][j]) ans.push([i, j]);
if (i - 1 >= 0 && M[i - 1][j] >= M[i][j]) dfs(visited, i - 1, j,);
if (i + 1 < n && M[i + 1][j] >= M[i][j]) dfs(visited, i + 1, j,);
if (j - 1 >= 0 && M[i][j - 1] >= M[i][j]) dfs(visited, i, j - 1,);
if (j + 1 < m && M[i][j + 1] >= M[i][j]) dfs(visited, i, j + 1,);
}
for (let i = 0; i < n; ++i) {
dfs(P, i, 0,);
dfs(A, i, m - 1,);
}
for (let j = 0; j < m; ++j) {
dfs(P, 0, j,);
dfs(A, n - 1, j,);
}
return ans;
}