算法母题30道(第4篇):二叉树遍历(16-20)

系列说明:本系列共6篇文章,涵盖30道经典算法母题,使用Java实现
适用场景:算法学习、面试准备、编程能力提升


📋 30道算法母题总览

第1篇:数组与字符串基础(1-5)

  1. 两数之和
  2. 三数之和
  3. 盛最多水的容器
  4. 接雨水
  5. 最长无重复字符子串

第2篇:链表操作(6-10)

  1. 反转链表
  2. 合并两个有序链表
  3. 链表中倒数第k个节点
  4. 判断链表是否有环
  5. 合并K个有序链表

第3篇:栈与队列(11-15)

  1. 有效的括号
  2. 用栈实现队列
  3. 最小栈
  4. 滑动窗口最大值
  5. 每日温度

第4篇:二叉树遍历(16-20)⭐ 本篇

  1. 二叉树的前序遍历
  2. 二叉树的中序遍历
  3. 二叉树的后序遍历
  4. 二叉树的层序遍历
  5. 二叉树的最大深度

第5篇:动态规划(21-25)

  1. 爬楼梯
  2. 最长递增子序列
  3. 最长公共子序列
  4. 背包问题(0-1背包)
  5. 编辑距离

第6篇:搜索与排序(26-30)

  1. 二分查找
  2. 快速排序
  3. 归并排序
  4. 岛屿数量(DFS/BFS)
  5. 全排列(回溯)

二叉树节点定义

// 所有二叉树题目的基础节点定义
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    
    TreeNode() {}
    TreeNode(int val) { this.val = val; }
    TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}

第16题:二叉树的前序遍历

题目描述

给你二叉树的根节点 root,返回它节点值的前序遍历

前序遍历顺序:根节点 → 左子树 → 右子树

示例

输入:root = [1,null,2,3]
    1
     \
      2
     /
    3
输出:[1,2,3]

输入:root = [1,2,3,4,5,null,8,null,null,6,7,9]
输出:[1,2,4,5,6,7,3,8,9]

💡 解题思路

方法1:递归法(最直观)

  1. 访问根节点
  2. 递归遍历左子树
  3. 递归遍历右子树

方法2:迭代法(使用栈)

  1. 使用栈模拟递归过程
  2. 根节点入栈
  3. 弹出栈顶节点,访问它
  4. 先将右子节点入栈,再将左子节点入栈(注意顺序)
  5. 重复直到栈为空

🎯 日常应用

  • 表达式树:前缀表达式(波兰表达式)
  • 目录遍历:文件系统的前序遍历
  • 序列化:二叉树的序列化和反序列化
  • 复制树:先复制根节点再复制子树

🔧 代码解决的问题

  • 树的深度优先遍历
  • 递归和迭代的转换
  • 遍历顺序的控制

Java实现代码

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class PreorderTraversal {
    
    /**
     * 前序遍历 - 递归法
     * @param root 二叉树根节点
     * @return 前序遍历结果
     */
    public List<Integer> preorderTraversalRecursive(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        preorder(root, result);
        return result;
    }
    
    private void preorder(TreeNode node, List<Integer> result) {
        if (node == null) {
            return;
        }
        
        // 访问根节点
        result.add(node.val);
        
        // 递归遍历左子树
        preorder(node.left, result);
        
        // 递归遍历右子树
        preorder(node.right, result);
    }
    
    /**
     * 前序遍历 - 迭代法(使用栈)
     */
    public List<Integer> preorderTraversalIterative(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null) {
            return result;
        }
        
        Stack<TreeNode> stack = new Stack<>();
        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;
    }
    
    // 辅助方法:创建二叉树(用于测试)
    public TreeNode createTree(Integer[] arr) {
        if (arr == null || arr.length == 0 || arr[0] == null) {
            return null;
        }
        
        TreeNode root = new TreeNode(arr[0]);
        java.util.Queue<TreeNode> queue = new java.util.LinkedList<>();
        queue.offer(root);
        
        int i = 1;
        while (!queue.isEmpty() && i < arr.length) {
            TreeNode node = queue.poll();
            
            if (i < arr.length && arr[i] != null) {
                node.left = new TreeNode(arr[i]);
                queue.offer(node.left);
            }
            i++;
            
            if (i < arr.length && arr[i] != null) {
                node.right = new TreeNode(arr[i]);
                queue.offer(node.right);
            }
            i++;
        }
        
        return root;
    }
    
    // 测试方法
    public static void main(String[] args) {
        PreorderTraversal solution = new PreorderTraversal();
        
        Integer[] arr = {1, null, 2, 3};
        TreeNode root = solution.createTree(arr);
        
        System.out.println("递归法前序遍历: " + 
            solution.preorderTraversalRecursive(root));
        System.out.println("迭代法前序遍历: " + 
            solution.preorderTraversalIterative(root));
    }
}

⏱️ 复杂度分析

  • 时间复杂度:O(n)

    • n 是二叉树的节点数
    • 每个节点访问一次
  • 空间复杂度

    • 递归法:O(h),h 是树的高度
      • 最坏情况(链状):O(n)
      • 最好情况(平衡):O(log n)
    • 迭代法:O(h),栈的最大深度为 h

第17题:二叉树的中序遍历

📝 题目描述

给定一个二叉树的根节点 root,返回它的中序遍历

中序遍历顺序:左子树 → 根节点 → 右子树

示例

输入:root = [1,null,2,3]
    1
     \
      2
     /
    3
输出:[1,3,2]

输入:root = []
输出:[]

输入:root = [1]
输出:[1]

解题思路

方法1:递归法

  1. 递归遍历左子树
  2. 访问根节点
  3. 递归遍历右子树

方法2:迭代法(使用栈)

  1. 沿左子树一路向下,将所有左节点入栈
  2. 弹出栈顶节点,访问它
  3. 转向右子树,重复步骤1
  4. 直到栈为空且当前节点为空

重要特性

  • 对于二叉搜索树(BST),中序遍历得到有序序列

日常应用

  • 二叉搜索树:获取有序数据
  • 表达式树:中缀表达式(正常数学表达式)
  • 数据排序:BST 的排序输出
  • 范围查询:在 BST 中查找某个范围的节点

代码解决的问题

  • 二叉搜索树的有序遍历
  • 中缀表达式的生成
  • 树形数据的线性化

💻 Java实现代码

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class InorderTraversal {
    
    /**
     * 中序遍历 - 递归法
     * @param root 二叉树根节点
     * @return 中序遍历结果
     */
    public List<Integer> inorderTraversalRecursive(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        inorder(root, result);
        return result;
    }
    
    private void inorder(TreeNode node, List<Integer> result) {
        if (node == null) {
            return;
        }
        
        // 递归遍历左子树
        inorder(node.left, result);
        
        // 访问根节点
        result.add(node.val);
        
        // 递归遍历右子树
        inorder(node.right, result);
    }
    
    /**
     * 中序遍历 - 迭代法(使用栈)
     */
    public List<Integer> inorderTraversalIterative(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<>();
        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;
    }
    
    // 辅助方法:创建二叉搜索树(用于测试)
    public TreeNode createBST(int[] arr) {
        if (arr == null || arr.length == 0) {
            return null;
        }
        return buildBST(arr, 0, arr.length - 1);
    }
    
    private TreeNode buildBST(int[] arr, int left, int right) {
        if (left > right) {
            return null;
        }
        
        int mid = left + (right - left) / 2;
        TreeNode node = new TreeNode(arr[mid]);
        node.left = buildBST(arr, left, mid - 1);
        node.right = buildBST(arr, mid + 1, right);
        
        return node;
    }
    
    // 测试方法
    public static void main(String[] args) {
        InorderTraversal solution = new InorderTraversal();
        
        // 创建二叉搜索树
        int[] arr = {1, 2, 3, 4, 5, 6, 7};
        TreeNode root = solution.createBST(arr);
        
        System.out.println("二叉搜索树的中序遍历(应该是有序的):");
        System.out.println("递归法: " + solution.inorderTraversalRecursive(root));
        System.out.println("迭代法: " + solution.inorderTraversalIterative(root));
    }
}

⏱️ 复杂度分析

  • 时间复杂度:O(n)

    • 每个节点访问一次
  • 空间复杂度:O(h)

    • h 是树的高度
    • 栈的最大深度为 h

第18题:二叉树的后序遍历

题目描述

给你一棵二叉树的根节点 root,返回其节点值的后序遍历

后序遍历顺序:左子树 → 右子树 → 根节点

示例

输入:root = [1,null,2,3]
    1
     \
      2
     /
    3
输出:[3,2,1]

输入:root = [1,2,3,4,5,null,8,null,null,6,7,9]
输出:[4,6,7,5,2,9,8,3,1]

💡 解题思路

方法1:递归法

  1. 递归遍历左子树
  2. 递归遍历右子树
  3. 访问根节点

方法2:迭代法(使用栈)

  1. 使用栈和两个指针:currlastVisited
  2. 沿左子树向下,节点入栈
  3. 查看栈顶节点:
    • 如果右子树存在且未被访问,转向右子树
    • 否则访问当前节点,标记为已访问
  4. 重复直到栈为空

方法3:前序遍历的变种

  1. 修改前序遍历:根 → 右 → 左
  2. 将结果反转:得到左 → 右 → 根

日常应用

  • 表达式树:后缀表达式(逆波兰表达式)
  • 删除树:先删除子树再删除根节点
  • 释放内存:C/C++ 中的树节点释放
  • 目录删除:先删除子目录再删除父目录

🔧 代码解决的问题

  • 后续处理(先处理子节点再处理父节点)
  • 表达式的后缀形式
  • 树的销毁操作

💻 Java实现代码

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;

public class PostorderTraversal {
    
    /**
     * 后序遍历 - 递归法
     * @param root 二叉树根节点
     * @return 后序遍历结果
     */
    public List<Integer> postorderTraversalRecursive(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        postorder(root, result);
        return result;
    }
    
    private void postorder(TreeNode node, List<Integer> result) {
        if (node == null) {
            return;
        }
        
        // 递归遍历左子树
        postorder(node.left, result);
        
        // 递归遍历右子树
        postorder(node.right, result);
        
        // 访问根节点
        result.add(node.val);
    }
    
    /**
     * 后序遍历 - 迭代法(使用栈)
     */
    public List<Integer> postorderTraversalIterative(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null) {
            return result;
        }
        
        Stack<TreeNode> stack = new Stack<>();
        TreeNode curr = root;
        TreeNode lastVisited = null;
        
        while (curr != null || !stack.isEmpty()) {
            // 沿左子树向下
            while (curr != null) {
                stack.push(curr);
                curr = curr.left;
            }
            
            // 查看栈顶节点
            curr = stack.peek();
            
            // 如果右子树存在且未被访问
            if (curr.right != null && lastVisited != curr.right) {
                curr = curr.right;
            } else {
                // 访问当前节点
                result.add(curr.val);
                lastVisited = curr;
                stack.pop();
                curr = null; // 防止重复处理左子树
            }
        }
        
        return result;
    }
    
    /**
     * 后序遍历 - 前序遍历变种法(最简洁)
     */
    public List<Integer> postorderTraversalReverse(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null) {
            return result;
        }
        
        Stack<TreeNode> stack = new Stack<>();
        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;
    }
    
    // 测试方法
    public static void main(String[] args) {
        PostorderTraversal solution = new PostorderTraversal();
        
        // 创建树: [1,null,2,3]
        TreeNode root = new TreeNode(1);
        root.right = new TreeNode(2);
        root.right.left = new TreeNode(3);
        
        System.out.println("递归法后序遍历: " + 
            solution.postorderTraversalRecursive(root));
        System.out.println("迭代法后序遍历: " + 
            solution.postorderTraversalIterative(root));
        System.out.println("反转法后序遍历: " + 
            solution.postorderTraversalReverse(root));
    }
}

⏱️ 复杂度分析

  • 时间复杂度:O(n)

    • 每个节点访问一次
  • 空间复杂度:O(h)

    • h 是树的高度
    • 栈的最大深度为 h

第19题:二叉树的层序遍历

📝 题目描述

给你二叉树的根节点 root,返回其节点值的层序遍历。(即逐层地,从左到右访问所有节点)

示例

输入:root = [3,9,20,null,null,15,7]
    3
   / \
  9  20
    /  \
   15   7
输出:[[3],[9,20],[15,7]]

输入:root = [1]
输出:[[1]]

输入:root = []
输出:[]

解题思路

广度优先搜索(BFS)+ 队列

  1. 使用队列存储每一层的节点
  2. 初始时将根节点入队
  3. 对于每一层:
    • 记录当前层的节点数量(队列大小)
    • 遍历这些节点,将值加入当前层结果
    • 将子节点入队
  4. 将当前层结果加入最终结果
  5. 重复直到队列为空

🎯 日常应用

  • 文件系统:目录的逐层遍历
  • 网络爬虫:网页的广度优先爬取
  • 社交网络:好友的逐层推荐
  • 游戏AI:状态空间的广度搜索

代码解决的问题

  • 树的层次遍历
  • 按层组织数据
  • 最短路径问题(BFS)

💻 Java实现代码

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class LevelOrderTraversal {
    
    /**
     * 层序遍历 - BFS(使用队列)
     * @param root 二叉树根节点
     * @return 层序遍历结果(二维列表)
     */
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        if (root == null) {
            return result;
        }
        
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        
        while (!queue.isEmpty()) {
            // 当前层的节点数量
            int levelSize = queue.size();
            List<Integer> currentLevel = new ArrayList<>();
            
            // 遍历当前层的所有节点
            for (int i = 0; i < levelSize; i++) {
                TreeNode node = queue.poll();
                currentLevel.add(node.val);
                
                // 将子节点加入队列
                if (node.left != null) {
                    queue.offer(node.left);
                }
                if (node.right != null) {
                    queue.offer(node.right);
                }
            }
            
            // 将当前层加入结果
            result.add(currentLevel);
        }
        
        return result;
    }
    
    /**
     * 层序遍历 - DFS(递归法)
     */
    public List<List<Integer>> levelOrderDFS(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        if (root == null) {
            return result;
        }
        
        dfs(root, 0, result);
        return result;
    }
    
    private void dfs(TreeNode node, int level, List<List<Integer>> result) {
        if (node == null) {
            return;
        }
        
        // 如果是新的一层,创建新列表
        if (result.size() == level) {
            result.add(new ArrayList<>());
        }
        
        // 将当前节点加入对应层
        result.get(level).add(node.val);
        
        // 递归处理子节点
        dfs(node.left, level + 1, result);
        dfs(node.right, level + 1, result);
    }
    
    // 测试方法
    public static void main(String[] args) {
        LevelOrderTraversal solution = new LevelOrderTraversal();
        
        // 创建树: [3,9,20,null,null,15,7]
        TreeNode root = new TreeNode(3);
        root.left = new TreeNode(9);
        root.right = new TreeNode(20);
        root.right.left = new TreeNode(15);
        root.right.right = new TreeNode(7);
        
        System.out.println("BFS层序遍历: " + solution.levelOrder(root));
        System.out.println("DFS层序遍历: " + solution.levelOrderDFS(root));
    }
}

⏱️ 复杂度分析

  • 时间复杂度:O(n)

    • n 是二叉树的节点数
    • 每个节点访问一次
  • 空间复杂度:O(n)

    • 队列最多存储一层的节点
    • 最坏情况(完全二叉树的最后一层):O(n/2) = O(n)

第20题:二叉树的最大深度

题目描述

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

示例

给定二叉树 [3,9,20,null,null,15,7],
    3
   / \
  9  20
    /  \
   15   7
返回它的最大深度 3。

解题思路

方法1:递归法(深度优先搜索)

  1. 如果节点为空,深度为 0
  2. 递归计算左子树的深度
  3. 递归计算右子树的深度
  4. 最大深度 = max(左子树深度, 右子树深度) + 1

方法2:迭代法(广度优先搜索)

  1. 使用队列进行层序遍历
  2. 每遍历完一层,深度 +1
  3. 直到队列为空

🎯 日常应用

  • 树的平衡检测:判断树是否平衡
  • 性能优化:评估树的高度对性能的影响
  • 决策树:评估决策树的复杂度
  • 游戏树:计算搜索深度

代码解决的问题

  • 树的高度计算
  • 递归的终止条件
  • DFS 和 BFS 的应用

💻 Java实现代码

import java.util.LinkedList;
import java.util.Queue;

public class MaximumDepthOfBinaryTree {
    
    /**
     * 二叉树的最大深度 - 递归法(DFS)
     * @param root 二叉树根节点
     * @return 最大深度
     */
    public int maxDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        
        // 递归计算左右子树的深度
        int leftDepth = maxDepth(root.left);
        int rightDepth = maxDepth(root.right);
        
        // 返回较大值 + 1(当前节点)
        return Math.max(leftDepth, rightDepth) + 1;
    }
    
    /**
     * 二叉树的最大深度 - 迭代法(BFS)
     */
    public int maxDepthBFS(TreeNode root) {
        if (root == null) {
            return 0;
        }
        
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        int depth = 0;
        
        while (!queue.isEmpty()) {
            // 当前层的节点数量
            int levelSize = queue.size();
            
            // 遍历当前层
            for (int i = 0; i < levelSize; i++) {
                TreeNode node = queue.poll();
                
                if (node.left != null) {
                    queue.offer(node.left);
                }
                if (node.right != null) {
                    queue.offer(node.right);
                }
            }
            
            // 完成一层,深度 +1
            depth++;
        }
        
        return depth;
    }
    
    /**
     * 二叉树的最小深度(扩展)
     * 最小深度是从根节点到最近叶子节点的最短路径上的节点数量
     */
    public int minDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        
        // 如果左子树为空,只考虑右子树
        if (root.left == null) {
            return minDepth(root.right) + 1;
        }
        
        // 如果右子树为空,只考虑左子树
        if (root.right == null) {
            return minDepth(root.left) + 1;
        }
        
        // 左右子树都不为空,取较小值
        return Math.min(minDepth(root.left), minDepth(root.right)) + 1;
    }
    
    // 测试方法
    public static void main(String[] args) {
        MaximumDepthOfBinaryTree solution = new MaximumDepthOfBinaryTree();
        
        // 创建树: [3,9,20,null,null,15,7]
        TreeNode root = new TreeNode(3);
        root.left = new TreeNode(9);
        root.right = new TreeNode(20);
        root.right.left = new TreeNode(15);
        root.right.right = new TreeNode(7);
        
        System.out.println("最大深度(DFS): " + solution.maxDepth(root));
        System.out.println("最大深度(BFS): " + solution.maxDepthBFS(root));
        System.out.println("最小深度: " + solution.minDepth(root));
    }
}

⏱️ 复杂度分析

递归法

  • 时间复杂度:O(n)

    • 每个节点访问一次
  • 空间复杂度:O(h)

    • h 是树的高度
    • 递归调用栈的深度为 h
    • 最坏情况(链状):O(n)
    • 最好情况(平衡):O(log n)

迭代法

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
    • 队列最多存储一层的节点

总结与对比

二叉树遍历技巧总结

题目 遍历顺序 核心方法 时间复杂度 空间复杂度
前序遍历 根→左→右 递归/栈 O(n) O(h)
中序遍历 左→根→右 递归/栈 O(n) O(h)
后序遍历 左→右→根 递归/栈 O(n) O(h)
层序遍历 逐层 队列(BFS) O(n) O(n)
最大深度 - 递归(DFS) O(n) O(h)

遍历顺序对比

        1
       / \
      2   3
     / \
    4   5

前序遍历: 1 → 2 → 4 → 5 → 3
中序遍历: 4 → 2 → 5 → 1 → 3
后序遍历: 4 → 5 → 2 → 3 → 1
层序遍历: [1], [2, 3], [4, 5]

通用解题模板

1. 递归遍历模板
void traverse(TreeNode node) {
    if (node == null) return;
    
    // 前序位置(访问根节点)
    // visit(node.val);
    
    traverse(node.left);
    
    // 中序位置(访问根节点)
    // visit(node.val);
    
    traverse(node.right);
    
    // 后序位置(访问根节点)
    // visit(node.val);
}
2. 迭代遍历模板(前序)
Stack<TreeNode> stack = new Stack<>();
stack.push(root);

while (!stack.isEmpty()) {
    TreeNode node = stack.pop();
    // visit(node.val);  // 前序
    
    if (node.right != null) stack.push(node.right);
    if (node.left != null) stack.push(node.left);
}
3. 层序遍历模板
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);

while (!queue.isEmpty()) {
    int size = queue.size();
    for (int i = 0; i < size; i++) {
        TreeNode node = queue.poll();
        // visit(node.val);
        
        if (node.left != null) queue.offer(node.left);
        if (node.right != null) queue.offer(node.right);
    }
}
4. 树的最大深度模板
int maxDepth(TreeNode root) {
    if (root == null) return 0;
    return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}

📚 下一篇预告

第5篇:动态规划(21-25)

  • 爬楼梯
  • 最长递增子序列
  • 最长公共子序列
  • 背包问题(0-1背包)
  • 编辑距离

如果觉得这篇文章对您有帮助,欢迎:

  • 👍 点赞支持
  • 💬 留言讨论
  • ⭐ 收藏备用

有任何算法问题,欢迎在评论区交流!

更多推荐