前端树结构算法与高频面试题精解

一、树结构基础与前端场景

树(Tree)是一种非线性数据结构,由根节点、子节点和边组成,具有层级关系。在前端开发中,树结构的应用极为广泛,例如DOM树(页面元素层级)、虚拟DOM的差异计算、组件树的递归渲染、路由配置的嵌套规则,以及状态管理中的嵌套数据结构。

树的核心特性包括:

  • 层级性:每个节点(除根节点)有且仅有一个父节点;
  • 递归性:子树本身也是一棵树,适合用递归处理;
  • 多样性:二叉树、多叉树、平衡树等变种适用于不同场景。

例如,React/Vue的组件树通过递归渲染实现嵌套组件的展示,而路由配置中的嵌套路由本质上是树结构的路径匹配。理解树结构的基础操作(遍历、搜索、修改)是解决前端复杂问题的关键。

二、8道高频树算法面试题解析

1. 树的深度优先遍历(DFS)与广度优先遍历(BFS)

问题:如何实现树的深度优先和广度优先遍历?
解析

  • DFS:递归或栈实现,优先访问子节点。适用于需要深度搜索的场景(如查找特定节点)。
  • BFS:队列实现,按层级访问节点。适用于最短路径或层级相关问题(如计算树的最大宽度)。

代码示例(DFS递归)

  1. function dfs(node, callback) {
  2. if (!node) return;
  3. callback(node.value); // 前序遍历
  4. dfs(node.left, callback);
  5. dfs(node.right, callback);
  6. // callback(node.value); // 后序遍历(调整位置)
  7. }

优化点:递归可能导致栈溢出,可改用显式栈实现迭代DFS。

2. 二叉树的最大深度

问题:给定二叉树,求其最大深度(根节点到最远叶子节点的路径长度)。
解析:递归计算左右子树的最大深度,取较大值加1。时间复杂度O(n),空间复杂度O(h)(h为树高)。

代码示例

  1. function maxDepth(root) {
  2. if (!root) return 0;
  3. return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
  4. }

3. 对称二叉树判断

问题:判断一棵二叉树是否对称(镜像对称)。
解析:递归比较左子树的左节点与右子树的右节点,左子树的右节点与右子树的左节点。

代码示例

  1. function isSymmetric(root) {
  2. if (!root) return true;
  3. const isMirror = (left, right) => {
  4. if (!left && !right) return true;
  5. if (!left || !right) return false;
  6. return left.val === right.val &&
  7. isMirror(left.left, right.right) &&
  8. isMirror(left.right, right.left);
  9. };
  10. return isMirror(root.left, root.right);
  11. }

4. 路径总和

问题:给定二叉树和一个目标和,判断是否存在从根节点到叶子节点的路径,其节点值之和等于目标和。
解析:递归计算路径和,若到达叶子节点且和等于目标则返回true。

代码示例

  1. function hasPathSum(root, targetSum) {
  2. if (!root) return false;
  3. if (!root.left && !root.right) return targetSum === root.val;
  4. return hasPathSum(root.left, targetSum - root.val) ||
  5. hasPathSum(root.right, targetSum - root.val);
  6. }

5. 二叉树的层序遍历

问题:按层级打印二叉树的节点值。
解析:使用队列实现BFS,记录每层的节点数。

代码示例

  1. function levelOrder(root) {
  2. if (!root) return [];
  3. const queue = [root], result = [];
  4. while (queue.length) {
  5. const levelSize = queue.length;
  6. const currentLevel = [];
  7. for (let i = 0; i < levelSize; i++) {
  8. const node = queue.shift();
  9. currentLevel.push(node.val);
  10. if (node.left) queue.push(node.left);
  11. if (node.right) queue.push(node.right);
  12. }
  13. result.push(currentLevel);
  14. }
  15. return result;
  16. }

6. 最近公共祖先(LCA)

问题:给定二叉树和两个节点p、q,求它们的最近公共祖先。
解析:递归查找p和q,若当前节点为p或q,或左右子树分别包含p和q,则当前节点为LCA。

代码示例

  1. function lowestCommonAncestor(root, p, q) {
  2. if (!root || root === p || root === q) return root;
  3. const left = lowestCommonAncestor(root.left, p, q);
  4. const right = lowestCommonAncestor(root.right, p, q);
  5. if (left && right) return root;
  6. return left || right;
  7. }

7. 序列化与反序列化二叉树

问题:将二叉树序列化为字符串,并能从字符串还原二叉树。
解析:前序遍历序列化,用特殊字符(如”#”)表示空节点;反序列化时递归构建树。

代码示例

  1. // 序列化
  2. function serialize(root) {
  3. if (!root) return "#";
  4. return `${root.val},${serialize(root.left)},${serialize(root.right)}`;
  5. }
  6. // 反序列化
  7. function deserialize(data) {
  8. const nodes = data.split(",");
  9. let index = 0;
  10. const buildTree = () => {
  11. const val = nodes[index++];
  12. if (val === "#") return null;
  13. const node = { val: parseInt(val), left: null, right: null };
  14. node.left = buildTree();
  15. node.right = buildTree();
  16. return node;
  17. };
  18. return buildTree();
  19. }

8. 平衡二叉树判断

问题:判断二叉树是否为平衡二叉树(任意节点的左右子树高度差不超过1)。
解析:自底向上递归,计算子树高度并判断平衡性。

代码示例

  1. function isBalanced(root) {
  2. const checkHeight = (node) => {
  3. if (!node) return 0;
  4. const leftHeight = checkHeight(node.left);
  5. const rightHeight = checkHeight(node.right);
  6. if (leftHeight === -1 || rightHeight === -1 ||
  7. Math.abs(leftHeight - rightHeight) > 1) {
  8. return -1;
  9. }
  10. return 1 + Math.max(leftHeight, rightHeight);
  11. };
  12. return checkHeight(root) !== -1;
  13. }

三、性能优化与最佳实践

  1. 递归转迭代:对于深度较大的树,递归可能导致栈溢出,可用显式栈或队列替代。
  2. 剪枝优化:在搜索类问题中,提前终止无效分支(如路径和中已超过目标和时直接返回)。
  3. 缓存子问题结果:对于重复计算的子树(如平衡树的高度),可缓存结果避免重复计算。
  4. 空间复杂度分析:BFS的空间复杂度为O(w)(w为最大宽度),DFS为O(h)(h为高度),根据场景选择。

四、总结与刷题建议

树结构算法是前端面试的高频考点,掌握其核心思想(递归、分治、层级处理)和常见变种(二叉树、多叉树)至关重要。建议通过以下方式提升能力:

  1. 分题型练习:按遍历、搜索、修改等类别分类刷题;
  2. 代码模板化:总结DFS/BFS的代码模板,快速适配不同问题;
  3. 复杂度分析:每道题分析时间与空间复杂度,培养优化思维。

通过系统练习,开发者不仅能高效通过面试,还能在实际项目中灵活应用树结构解决复杂问题。