前言
深度优先搜索(DFS)和广度优先搜索(BFS)是算法面试中的核心知识点。本文将通过10个经典的LeetCode题目,深入解析这两种搜索算法的应用场景、解题思路和实现技巧。
1. 二叉树的最大深度 - LeetCode 104
题目描述
给定一个二叉树,找出其最大深度。
解法一:递归DFS
javascript
// DFS递归解法
function maxDepth(root) {
if (!root) return 0;
return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}
解法二:迭代BFS
javascript
// BFS迭代解法
function maxDepth(root) {
if (!root) return 0;
let queue = [root];
let depth = 0;
while (queue.length > 0) {
const levelSize = queue.length;
depth++;
for (let i = 0; i < levelSize; i++) {
const node = queue.shift();
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
}
return depth;
}
2. 二叉树的层序遍历 - LeetCode 102
题目描述
给你二叉树的根节点 root ,返回其节点值的层序遍历。
BFS解法
javascript
function levelOrder(root) {
if (!root) return [];
const result = [];
const queue = [root];
while (queue.length > 0) {
const level = [];
const levelSize = queue.length;
for (let i = 0; i < levelSize; i++) {
const node = queue.shift();
level.push(node.val);
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
result.push(level);
}
return result;
}
3. 路径总和 - LeetCode 112
题目描述
判断是否存在从根节点到叶子节点的路径,使得路径上所有节点值之和等于目标和。
DFS解法
javascript
function hasPathSum(root, targetSum) {
if (!root) return false;
// 如果是叶子节点,检查是否等于剩余目标值
if (!root.left && !root.right) {
return root.val === targetSum;
}
// 递归检查左右子树
return hasPathSum(root.left, targetSum - root.val) ||
hasPathSum(root.right, targetSum - root.val);
}
4. 岛屿数量 - LeetCode 200
题目描述
给你一个由 '1'(陆地)和 '0'(水)组成的二维网格,请你计算网格中岛屿的数量。
DFS解法
javascript
function numIslands(grid) {
if (!grid || grid.length === 0) return 0;
let count = 0;
const rows = grid.length;
const cols = grid[0].length;
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
if (grid[i][j] === '1') {
count++;
dfs(grid, i, j);
}
}
}
return count;
}
function dfs(grid, i, j) {
const rows = grid.length;
const cols = grid[0].length;
// 边界检查和水域检查
if (i < 0 || i >= rows || j < 0 || j >= cols || grid[i][j] === '0') {
return;
}
// 标记已访问
grid[i][j] = '0';
// 四个方向递归搜索
dfs(grid, i + 1, j);
dfs(grid, i - 1, j);
dfs(grid, i, j + 1);
dfs(grid, i, j - 1);
}
5. 单词搜索 - LeetCode 79
题目描述
给定一个 m x n 二维字符网格 board 和一个字符串单词 word。
DFS回溯解法
javascript
function exist(board, word) {
if (!board || board.length === 0) return false;
const rows = board.length;
const cols = board[0].length;
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
if (dfs(board, word, i, j, 0)) {
return true;
}
}
}
return false;
}
function dfs(board, word, i, j, index) {
// 边界检查
if (i < 0 || i >= board.length ||
j < 0 || j >= board[0].length ||
board[i][j] !== word[index]) {
return false;
}
// 找到完整单词
if (index === word.length - 1) {
return true;
}
// 标记当前字符为已访问
const temp = board[i][j];
board[i][j] = '#';
// 四个方向搜索
const found = dfs(board, word, i + 1, j, index + 1) ||
dfs(board, word, i - 1, j, index + 1) ||
dfs(board, word, i, j + 1, index + 1) ||
dfs(board, word, i, j - 1, index + 1);
// 回溯,恢复原字符
board[i][j] = temp;
return found;
}
6. 克隆图 - LeetCode 133
题目描述
给你无向连通图中一个节点的引用,请你返回该图的深拷贝。
BFS解法
javascript
function cloneGraph(node) {
if (!node) return null;
const visited = new Map();
const queue = [node];
// 克隆第一个节点
const cloneNode = new Node(node.val);
visited.set(node, cloneNode);
while (queue.length > 0) {
const current = queue.shift();
const cloneCurrent = visited.get(current);
// 处理邻居节点
for (const neighbor of current.neighbors) {
if (!visited.has(neighbor)) {
// 克隆邻居节点
const cloneNeighbor = new Node(neighbor.val);
visited.set(neighbor, cloneNeighbor);
queue.push(neighbor);
}
// 建立连接关系
cloneCurrent.neighbors.push(visited.get(neighbor));
}
}
return cloneNode;
}
7. 被围绕的区域 - LeetCode 130
题目描述
给定一个二维矩阵,将被 'X' 包围的 'O' 替换为 'X'。
DFS解法
javascript
function solve(board) {
if (!board || board.length === 0) return;
const rows = board.length;
const cols = board[0].length;
// 从边界上的'O'开始DFS标记
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
// 边界上的'O'
if ((i === 0 || i === rows - 1 || j === 0 || j === cols - 1) &&
board[i][j] === 'O') {
dfs(board, i, j);
}
}
}
// 处理结果
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
if (board[i][j] === 'O') {
board[i][j] = 'X'; // 未被标记的'O'变为'X'
} else if (board[i][j] === 'T') {
board[i][j] = 'O'; // 被标记的'T'恢复为'O'
}
}
}
}
function dfs(board, i, j) {
const rows = board.length;
const cols = board[0].length;
if (i < 0 || i >= rows || j < 0 || j >= cols || board[i][j] !== 'O') {
return;
}
board[i][j] = 'T'; // 标记为临时状态
// 四个方向DFS
dfs(board, i + 1, j);
dfs(board, i - 1, j);
dfs(board, i, j + 1);
dfs(board, i, j - 1);
}
8. 二叉树的右视图 - LeetCode 199
题目描述
给定一个二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
BFS解法
javascript
function rightSideView(root) {
if (!root) return [];
const result = [];
const queue = [root];
while (queue.length > 0) {
const levelSize = queue.length;
for (let i = 0; i < levelSize; i++) {
const node = queue.shift();
// 每层最后一个节点就是右视图可见节点
if (i === levelSize - 1) {
result.push(node.val);
}
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
}
return result;
}
9. 矩阵中的最长递增路径 - LeetCode 329
题目描述
给定一个 m x n 整数矩阵,返回从任意单元格开始的所有递增路径中,最长路径的长度。
DFS+记忆化解法
javascript
function longestIncreasingPath(matrix) {
if (!matrix || matrix.length === 0) return 0;
const rows = matrix.length;
const cols = matrix[0].length;
const memo = Array(rows).fill().map(() => Array(cols).fill(0));
let maxLength = 0;
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
maxLength = Math.max(maxLength, dfs(matrix, i, j, memo));
}
}
return maxLength;
}
function dfs(matrix, i, j, memo) {
if (memo[i][j] !== 0) return memo[i][j];
const directions = [[-1, 0], [1, 0], [0, -1], [0, 1]];
memo[i][j] = 1; // 至少包含自己
for (const [di, dj] of directions) {
const ni = i + di;
const nj = j + dj;
if (ni >= 0 && ni < matrix.length &&
nj >= 0 && nj < matrix[0].length &&
matrix[ni][nj] > matrix[i][j]) {
memo[i][j] = Math.max(memo[i][j], 1 + dfs(matrix, ni, nj, memo));
}
}
return memo[i][j];
}
10. 课程表 - LeetCode 207
题目描述
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1。
BFS拓扑排序解法
javascript
function canFinish(numCourses, prerequisites) {
// 构建邻接表和入度数组
const graph = Array(numCourses).fill().map(() => []);
const indegree = Array(numCourses).fill(0);
for (const [course, pre] of prerequisites) {
graph[pre].push(course);
indegree[course]++;
}
// 将入度为0的节点加入队列
const queue = [];
for (let i = 0; i < numCourses; i++) {
if (indegree[i] === 0) {
queue.push(i);
}
}
let count = 0;
while (queue.length > 0) {
const current = queue.shift();
count++;
// 更新相邻节点的入度
for (const next of graph[current]) {
indegree[next]--;
if (indegree[next] === 0) {
queue.push(next);
}
}
}
return count === numCourses;
}
算法对比与选择指南
算法 | 时间复杂度 | 空间复杂度 | 适用场景 |
---|---|---|---|
DFS | O(V+E) | O(V) | 树形结构、路径查找、回溯 |
BFS | O(V+E) | O(V) | 层级遍历、最短路径、图的连通性 |
关键技巧总结
1. DFS常见模式
javascript
// 基本模板
function dfs(node) {
if (终止条件) return;
// 处理当前节点
// 递归调用
dfs(node.left);
dfs(node.right);
}
2. BFS常见模式
javascript
// 基本模板
function bfs(start) {
const queue = [start];
const visited = new Set();
while (queue.length > 0) {
const node = queue.shift();
if (visited.has(node)) continue;
visited.add(node);
// 处理当前节点
// 添加邻居节点
for (const neighbor of getNeighbors(node)) {
queue.push(neighbor);
}
}
}
3. 回溯技巧
javascript
// 回溯模板
function backtrack(path, choices) {
if (满足结束条件) {
result.add(path);
return;
}
for (每个选择) {
// 做选择
path.add(choice);
// 递归
backtrack(path, remainingChoices);
// 撤销选择
path.removeLast();
}
}
总结
DFS和BFS是解决图论和树相关问题的核心算法:
- DFS适用于:路径查找、连通性判断、回溯问题、树的遍历
- BFS适用于:最短路径、层级遍历、拓扑排序、状态空间搜索
掌握这些经典案例有助于在面试中快速识别问题类型并选择合适的解法策略。