news 2026/3/4 6:54:57

国家电网Java面试被问:二叉树的前序、中序、后序遍历

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
国家电网Java面试被问:二叉树的前序、中序、后序遍历

一、核心概念与区别

1. 三种遍历的直观理解

java

// 二叉树节点定义 class TreeNode { int val; TreeNode left; TreeNode right; TreeNode(int x) { val = x; } } /* 1 / \ 2 3 / \ \ 4 5 6 遍历顺序: - 前序 (Preorder): 根 → 左 → 右 - 中序 (Inorder): 左 → 根 → 右 - 后序 (Postorder): 左 → 右 → 根 */

2. 遍历结果示例

java

public class TraversalResults { /* 示例二叉树: A / \ B C / \ \ D E F 遍历结果: 前序:A → B → D → E → C → F 中序:D → B → E → A → C → F 后序:D → E → B → F → C → A */ }

二、递归实现(基础)

1. 前序遍历(根-左-右)

java

class Solution { // 递归实现 public List<Integer> preorderTraversal(TreeNode root) { List<Integer> result = new ArrayList<>(); preorder(root, result); return result; } private void preorder(TreeNode node, List<Integer> list) { if (node == null) return; list.add(node.val); // 1. 访问根节点 preorder(node.left, list); // 2. 遍历左子树 preorder(node.right, list); // 3. 遍历右子树 } }

2. 中序遍历(左-根-右)

java

class Solution { // 递归实现 public List<Integer> inorderTraversal(TreeNode root) { List<Integer> result = new ArrayList<>(); inorder(root, result); return result; } private void inorder(TreeNode node, List<Integer> list) { if (node == null) return; inorder(node.left, list); // 1. 遍历左子树 list.add(node.val); // 2. 访问根节点 inorder(node.right, list); // 3. 遍历右子树 } }

3. 后序遍历(左-右-根)

java

class Solution { // 递归实现 public List<Integer> postorderTraversal(TreeNode root) { List<Integer> result = new ArrayList<>(); postorder(root, result); return result; } private void postorder(TreeNode node, List<Integer> list) { if (node == null) return; postorder(node.left, list); // 1. 遍历左子树 postorder(node.right, list); // 2. 遍历右子树 list.add(node.val); // 3. 访问根节点 } }

三、迭代实现(面试重点)

1. 前序遍历迭代实现

java

// 方法1:使用栈(推荐) public List<Integer> preorderTraversalIterative(TreeNode root) { List<Integer> result = new ArrayList<>(); if (root == null) return result; Deque<TreeNode> stack = new ArrayDeque<>(); stack.push(root); while (!stack.isEmpty()) { TreeNode node = stack.pop(); result.add(node.val); // 访问根节点 // 先右后左,保证出栈时先左后右 if (node.right != null) { stack.push(node.right); } if (node.left != null) { stack.push(node.left); } } return result; } // 方法2:模拟递归栈 public List<Integer> preorderTraversalIterative2(TreeNode root) { List<Integer> result = new ArrayList<>(); Deque<TreeNode> stack = new ArrayDeque<>(); TreeNode curr = root; while (curr != null || !stack.isEmpty()) { // 一路向左,访问并入栈 while (curr != null) { result.add(curr.val); // 访问 stack.push(curr); // 入栈,用于后续访问右子树 curr = curr.left; } // 转向右子树 curr = stack.pop().right; } return result; }

2. 中序遍历迭代实现

java

// 标准迭代实现 public List<Integer> inorderTraversalIterative(TreeNode root) { List<Integer> result = new ArrayList<>(); Deque<TreeNode> stack = new ArrayDeque<>(); TreeNode curr = root; while (curr != null || !stack.isEmpty()) { // 一路向左入栈 while (curr != null) { stack.push(curr); curr = curr.left; } // 出栈访问 curr = stack.pop(); result.add(curr.val); // 访问根节点 // 转向右子树 curr = curr.right; } return result; }

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案
【点击此处即可/免费获取】​​​

3. 后序遍历迭代实现(难点)

java

// 方法1:使用两个栈(最易理解) public List<Integer> postorderTraversalTwoStacks(TreeNode root) { List<Integer> result = new ArrayList<>(); if (root == null) return result; Deque<TreeNode> stack1 = new ArrayDeque<>(); Deque<TreeNode> stack2 = new ArrayDeque<>(); stack1.push(root); // stack1 用于模拟"根-右-左"(前序变种) while (!stack1.isEmpty()) { TreeNode node = stack1.pop(); stack2.push(node); // 压入stack2 // 注意:左孩子先入栈,保证右孩子先被处理 if (node.left != null) { stack1.push(node.left); } if (node.right != null) { stack1.push(node.right); } } // stack2 出栈顺序即为"左-右-根" while (!stack2.isEmpty()) { result.add(stack2.pop().val); } return result; } // 方法2:使用一个栈 + 记录前驱节点(优化空间) public List<Integer> postorderTraversalOneStack(TreeNode root) { List<Integer> result = new ArrayList<>(); if (root == null) return result; Deque<TreeNode> stack = new ArrayDeque<>(); TreeNode curr = root; TreeNode lastVisited = null; // 记录上次访问的节点 while (curr != null || !stack.isEmpty()) { // 一路向左入栈 while (curr != null) { stack.push(curr); curr = curr.left; } TreeNode peekNode = stack.peek(); // 如果右子树存在且未被访问,转向右子树 if (peekNode.right != null && peekNode.right != lastVisited) { curr = peekNode.right; } else { // 否则,访问当前节点 result.add(peekNode.val); lastVisited = stack.pop(); } } return result; } // 方法3:前序遍历的逆序(技巧性) public List<Integer> postorderTraversalReverse(TreeNode root) { List<Integer> result = new ArrayList<>(); if (root == null) return result; Deque<TreeNode> stack = new ArrayDeque<>(); stack.push(root); // 执行"根-右-左"的遍历(前序遍历变种) while (!stack.isEmpty()) { TreeNode node = stack.pop(); result.add(node.val); // 注意:先左后右入栈,保证出栈时先右后左 if (node.left != null) { stack.push(node.left); } if (node.right != null) { stack.push(node.right); } } // 反转结果,得到"左-右-根" Collections.reverse(result); return result; }

四、Morris 遍历(O(1)空间)

1. Morris 前序遍历

java

public List<Integer> preorderTraversalMorris(TreeNode root) { List<Integer> result = new ArrayList<>(); TreeNode curr = root; while (curr != null) { if (curr.left == null) { // 如果没有左子树,直接访问并转向右子树 result.add(curr.val); curr = curr.right; } else { // 找到当前节点的前驱节点(左子树的最右节点) TreeNode predecessor = curr.left; while (predecessor.right != null && predecessor.right != curr) { predecessor = predecessor.right; } if (predecessor.right == null) { // 第一次到达,建立线索并访问当前节点 predecessor.right = curr; result.add(curr.val); // 访问 curr = curr.left; } else { // 第二次到达,说明左子树已访问完,恢复树结构 predecessor.right = null; curr = curr.right; } } } return result; }

2. Morris 中序遍历

java

public List<Integer> inorderTraversalMorris(TreeNode root) { List<Integer> result = new ArrayList<>(); TreeNode curr = root; while (curr != null) { if (curr.left == null) { // 没有左子树,访问当前节点并转向右子树 result.add(curr.val); curr = curr.right; } else { // 找到前驱节点 TreeNode predecessor = curr.left; while (predecessor.right != null && predecessor.right != curr) { predecessor = predecessor.right; } if (predecessor.right == null) { // 建立线索 predecessor.right = curr; curr = curr.left; } else { // 恢复树结构并访问当前节点 predecessor.right = null; result.add(curr.val); curr = curr.right; } } } return result; }

3. Morris 后序遍历(较复杂)

java

public List<Integer> postorderTraversalMorris(TreeNode root) { List<Integer> result = new ArrayList<>(); TreeNode dummy = new TreeNode(0); // 虚拟头节点 dummy.left = root; TreeNode curr = dummy; while (curr != null) { if (curr.left == null) { curr = curr.right; } else { // 找到前驱节点 TreeNode predecessor = curr.left; while (predecessor.right != null && predecessor.right != curr) { predecessor = predecessor.right; } if (predecessor.right == null) { // 建立线索 predecessor.right = curr; curr = curr.left; } else { // 恢复树结构,并倒序输出从curr.left到predecessor的路径 reverseAdd(curr.left, predecessor, result); predecessor.right = null; curr = curr.right; } } } return result; } // 倒序添加节点值 private void reverseAdd(TreeNode from, TreeNode to, List<Integer> result) { reverse(from, to); TreeNode node = to; while (true) { result.add(node.val); if (node == from) break; node = node.right; } reverse(to, from); } // 反转链表(从from到to的右指针路径) private void reverse(TreeNode from, TreeNode to) { if (from == to) return; TreeNode prev = from; TreeNode curr = from.right; while (prev != to) { TreeNode next = curr.right; curr.right = prev; prev = curr; curr = next; } }

五、实战应用与面试题

1. 根据遍历结果重建二叉树

java

// 前序+中序重建二叉树 public TreeNode buildTreeFromPreIn(int[] preorder, int[] inorder) { Map<Integer, Integer> inorderMap = new HashMap<>(); for (int i = 0; i < inorder.length; i++) { inorderMap.put(inorder[i], i); } return buildPreIn(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1, inorderMap); } private TreeNode buildPreIn(int[] preorder, int preStart, int preEnd, int[] inorder, int inStart, int inEnd, Map<Integer, Integer> inorderMap) { if (preStart > preEnd || inStart > inEnd) return null; TreeNode root = new TreeNode(preorder[preStart]); int inorderRootIndex = inorderMap.get(root.val); int leftSubtreeSize = inorderRootIndex - inStart; root.left = buildPreIn(preorder, preStart + 1, preStart + leftSubtreeSize, inorder, inStart, inorderRootIndex - 1, inorderMap); root.right = buildPreIn(preorder, preStart + leftSubtreeSize + 1, preEnd, inorder, inorderRootIndex + 1, inEnd, inorderMap); return root; } // 中序+后序重建二叉树 public TreeNode buildTreeFromInPost(int[] inorder, int[] postorder) { Map<Integer, Integer> inorderMap = new HashMap<>(); for (int i = 0; i < inorder.length; i++) { inorderMap.put(inorder[i], i); } return buildInPost(inorder, 0, inorder.length - 1, postorder, 0, postorder.length - 1, inorderMap); } private TreeNode buildInPost(int[] inorder, int inStart, int inEnd, int[] postorder, int postStart, int postEnd, Map<Integer, Integer> inorderMap) { if (inStart > inEnd || postStart > postEnd) return null; TreeNode root = new TreeNode(postorder[postEnd]); int inorderRootIndex = inorderMap.get(root.val); int leftSubtreeSize = inorderRootIndex - inStart; root.left = buildInPost(inorder, inStart, inorderRootIndex - 1, postorder, postStart, postStart + leftSubtreeSize - 1, inorderMap); root.right = buildInPost(inorder, inorderRootIndex + 1, inEnd, postorder, postStart + leftSubtreeSize, postEnd - 1, inorderMap); return root; }

2. 验证二叉搜索树(BST)

java

// 方法1:中序遍历验证(BST的中序是有序的) public boolean isValidBST(TreeNode root) { Deque<TreeNode> stack = new ArrayDeque<>(); TreeNode curr = root; Integer prev = null; while (curr != null || !stack.isEmpty()) { while (curr != null) { stack.push(curr); curr = curr.left; } curr = stack.pop(); // 检查当前值是否大于前一个值 if (prev != null && curr.val <= prev) { return false; } prev = curr.val; curr = curr.right; } return true; } // 方法2:递归验证 public boolean isValidBSTRecursive(TreeNode root) { return validate(root, null, null); } private boolean validate(TreeNode node, Integer min, Integer max) { if (node == null) return true; if ((min != null && node.val <= min) || (max != null && node.val >= max)) { return false; } return validate(node.left, min, node.val) && validate(node.right, node.val, max); }

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

java

public class Codec { // 前序遍历序列化 public String serialize(TreeNode root) { StringBuilder sb = new StringBuilder(); serializeHelper(root, sb); return sb.toString(); } private void serializeHelper(TreeNode node, StringBuilder sb) { if (node == null) { sb.append("null,"); return; } sb.append(node.val).append(","); serializeHelper(node.left, sb); serializeHelper(node.right, sb); } // 前序遍历反序列化 public TreeNode deserialize(String data) { Deque<String> nodes = new LinkedList<>(Arrays.asList(data.split(","))); return deserializeHelper(nodes); } private TreeNode deserializeHelper(Deque<String> nodes) { String val = nodes.removeFirst(); if (val.equals("null")) return null; TreeNode node = new TreeNode(Integer.parseInt(val)); node.left = deserializeHelper(nodes); node.right = deserializeHelper(nodes); return node; } }

4. 二叉树层次遍历变种

java

// 之字形遍历(锯齿形遍历) public List<List<Integer>> zigzagLevelOrder(TreeNode root) { List<List<Integer>> result = new ArrayList<>(); if (root == null) return result; Deque<TreeNode> queue = new LinkedList<>(); queue.offer(root); boolean leftToRight = true; while (!queue.isEmpty()) { int levelSize = queue.size(); List<Integer> level = new ArrayList<>(); for (int i = 0; i < levelSize; i++) { TreeNode node = queue.poll(); if (leftToRight) { level.add(node.val); } else { level.add(0, node.val); // 逆序添加 } if (node.left != null) queue.offer(node.left); if (node.right != null) queue.offer(node.right); } result.add(level); leftToRight = !leftToRight; } return result; }

六、复杂度分析与选择建议

1. 时间复杂度对比

算法时间复杂度空间复杂度适用场景
递归遍历O(n)O(h)(递归栈)简单实现,树高度不大
迭代遍历O(n)O(h)(显式栈)面试常用,可控性好
Morris遍历O(n)O(1)要求常数空间

2. 面试回答要点

java

// 面试时应展示的知识点 public class InterviewPoints { /* 前序遍历: - 应用:树的复制、序列化、目录结构显示 - 特点:第一个元素是根节点 中序遍历: - 应用:二叉搜索树得到有序序列、表达式树求值 - 特点:BST的中序是有序的 后序遍历: - 应用:计算目录大小、释放树内存、表达式树计算 - 特点:最后一个元素是根节点 迭代实现关键: - 前序:访问时机在入栈前 - 中序:访问时机在出栈时 - 后序:最难,可看作"根-右-左"的逆序 Morris遍历: - 优点:O(1)空间 - 缺点:修改了树结构(临时),适合只读场景 */ }

3. 常见面试题变形

  1. N叉树的前后序遍历(LeetCode 589, 590)

  2. 二叉搜索树迭代器(LeetCode 173)

  3. 验证前序遍历序列(LeetCode 255)

  4. 二叉树中所有距离为K的节点(LeetCode 863)

  5. 恢复二叉搜索树(LeetCode 99)

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案
【点击此处即可/免费获取】​​​


七、代码模板总结

通用迭代模板

java

// 二叉树遍历通用框架 public void traverse(TreeNode root) { // 初始化 Deque<TreeNode> stack = new ArrayDeque<>(); TreeNode curr = root; while (curr != null || !stack.isEmpty()) { // 处理左子树 while (curr != null) { // 前序:在这里访问 curr stack.push(curr); curr = curr.left; } curr = stack.pop(); // 中序:在这里访问 curr // 处理右子树 curr = curr.right; // 后序:需要额外的状态记录 } }

递归模板记忆口诀

java

/* 前序遍历:print -> left -> right 中序遍历:left -> print -> right 后序遍历:left -> right -> print 记忆口诀: 前序:我(根)在前面 中序:我(根)在中间 后序:我(根)在后面 */

总结:二叉树遍历是算法基础,必须熟练掌握递归、迭代、Morris三种实现。面试时不仅要能写出来,还要理解不同遍历方式的应用场景和复杂度分析。对于后序遍历的迭代实现要特别重视,这是常见难点。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/4 2:40:44

用药准时率提升90%?Open-AutoGLM时间提醒背后的算法秘密

第一章&#xff1a;用药准时率提升90%&#xff1f;Open-AutoGLM时间提醒背后的算法秘密在智能医疗领域&#xff0c;Open-AutoGLM 通过其创新的时间感知算法显著提升了患者用药准时率。该系统并非依赖简单闹钟机制&#xff0c;而是融合了用户行为建模、上下文感知与动态调度策略…

作者头像 李华
网站建设 2026/3/1 6:09:48

Open-AutoGLM体检报告集成实战(企业级应用案例深度剖析)

第一章&#xff1a;Open-AutoGLM体检报告集成实战概述在医疗信息化快速发展的背景下&#xff0c;Open-AutoGLM作为基于开源大语言模型的智能解析引擎&#xff0c;正逐步应用于体检报告的自动化解读与结构化输出。该系统通过融合自然语言理解与医学知识图谱&#xff0c;实现对非…

作者头像 李华
网站建设 2026/3/3 10:09:15

【Open-AutoGLM医院挂号预约系统揭秘】:如何用AI重构医疗排队难题

第一章&#xff1a;Open-AutoGLM医院挂号预约系统揭秘Open-AutoGLM 是一款基于大语言模型驱动的智能医院挂号预约系统&#xff0c;旨在通过自然语言理解与自动化流程编排&#xff0c;提升患者就医体验与医院运营效率。该系统能够解析用户以自然语言表达的挂号需求&#xff0c;自…

作者头像 李华
网站建设 2026/2/20 6:06:48

Open-AutoGLM核心技术解析:如何用自然语言理解破局政务“办事难”困局

第一章&#xff1a;Open-AutoGLM政务办理辅助的背景与意义随着数字化政府建设的不断推进&#xff0c;政务服务正从“可办”向“好办、智办”转型升级。传统的政务流程普遍存在手续繁琐、信息孤岛严重、跨部门协同效率低等问题&#xff0c;导致公众在办理业务时耗时长、体验差。…

作者头像 李华
网站建设 2026/3/3 23:13:36

你还在等客服回复?,掌握这3种自助预约方式秒杀90%用户

第一章&#xff1a;你还在等客服回复&#xff1f;掌握自助预约的必要性在数字化服务日益普及的今天&#xff0c;依赖人工客服处理预约请求不仅效率低下&#xff0c;还可能因响应延迟影响业务进度。掌握自助预约系统&#xff0c;意味着用户能够实时获取资源状态、自主安排时间&a…

作者头像 李华
网站建设 2026/3/1 17:19:50

多线程(0-0)

一、进程【理解】 1. 进程&#xff1a;操作系统(OS)中&#xff0c;每一个被执行的应用程序。 2. 注意&#xff1a;目前操作系统支持多进程&#xff0c;并发执行的任务。 3. 多进程并发执行的原理&#xff1a;微观上串行(一个一个的进程进行执行&#xff0c;获取cpu时间片的进程…

作者头像 李华