深入浅出Java算法树结构

深入浅出Java算法树结构

树结构算法题在面试中出现的频率特别高,今天我就用最通俗的大白话,给大家讲讲怎么搞定这些题目。

一、树题解题三板斧

遇到树的问题,先想这三招:

  1. 递归大法好:树天生适合递归,大部分问题都能用递归解决
  2. 迭代要用栈/队列:不想用递归?那就用栈或队列来模拟
  3. 分治思想:把大树拆成小树,分别解决再合并

二、高频考题解题套路

1. 求树的高度(最大深度)

问题:算算这棵树有几层?

递归思路

  • 如果树为空,高度为0
  • 否则高度 = 1 + max(左子树高度, 右子树高度)
java 复制代码
public int maxDepth(TreeNode root) {
    if (root == null) return 0;
    return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}

白话解释:问一个节点有多高,它说:"我看看我左右两个孩子谁更高,我就比它高一截"

2. 判断平衡二叉树

问题:这棵树左右两边高度差不超过1吗?

解题思路

  • 左右子树高度差 ≤ 1
  • 且左右子树也都是平衡树
java 复制代码
public boolean isBalanced(TreeNode root) {
    if (root == null) return true;
    return Math.abs(height(root.left) - height(root.right)) <= 1 
           && isBalanced(root.left) 
           && isBalanced(root.right);
}

private int height(TreeNode node) {
    if (node == null) return 0;
    return 1 + Math.max(height(node.left), height(node.right));
}

优化版(避免重复计算):

java 复制代码
public boolean isBalanced(TreeNode root) {
    return checkHeight(root) != -1;
}

private int checkHeight(TreeNode node) {
    if (node == null) return 0;
    
    int left = checkHeight(node.left);
    if (left == -1) return -1;
    
    int right = checkHeight(node.right);
    if (right == -1) return -1;
    
    if (Math.abs(left - right) > 1) return -1;
    return Math.max(left, right) + 1;
}

白话解释:让每个节点汇报自己是否平衡,如果不平衡就向上传递"不合格"信号

3. 判断对称二叉树

问题:这棵树左右对称吗?

解题思路

  • 根节点的左右子树是镜像关系
  • 左子树的左孩子 = 右子树的右孩子
  • 左子树的右孩子 = 右子树的左孩子
java 复制代码
public boolean isSymmetric(TreeNode root) {
    if (root == null) return true;
    return isMirror(root.left, root.right);
}

private boolean isMirror(TreeNode t1, TreeNode t2) {
    if (t1 == null && t2 == null) return true;
    if (t1 == null || t2 == null) return false;
    return (t1.val == t2.val) 
           && isMirror(t1.left, t2.right) 
           && isMirror(t1.right, t2.left);
}

白话解释:就像照镜子,左边抬起右手,镜子里应该是左边抬起左手

4. 最近公共祖先(LCA)

问题:找两个节点的最近共同祖先

解题思路

  • 如果当前节点是p或q,返回当前节点
  • 在左右子树中查找
  • 如果左右都找到,当前节点就是LCA
  • 如果只有一边找到,返回那一边的结果
java 复制代码
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    if (root == null || root == p || root == q) return root;
    
    TreeNode left = lowestCommonAncestor(root.left, p, q);
    TreeNode right = lowestCommonAncestor(root.right, p, q);
    
    if (left != null && right != null) return root;
    return left != null ? left : right;
}

白话解释:问每个节点:"你下面有这两个人吗?如果左右都有,你就是他们最近的共同领导"

5. 路径总和

问题:有没有一条从根到叶子的路径,节点值加起来等于目标和?

解题思路

  • 从根节点开始,每次用目标和减去当前节点值
  • 到达叶子节点时,看剩余和是否等于叶子节点值
java 复制代码
public boolean hasPathSum(TreeNode root, int sum) {
    if (root == null) return false;
    if (root.left == null && root.right == null && root.val == sum) return true;
    return hasPathSum(root.left, sum - root.val) 
           || hasPathSum(root.right, sum - root.val);
}

白话解释:带着钱从公司总部出发,每经过一个部门交一笔钱,到基层员工那里刚好把钱花完

三、二叉搜索树(BST)特殊题型

1. 验证BST

问题:这棵树真的是BST吗?

解题思路

  • 左子树所有节点 < 当前节点
  • 右子树所有节点 > 当前节点
  • 注意不能只比较父子节点!
java 复制代码
public boolean isValidBST(TreeNode root) {
    return isValid(root, Long.MIN_VALUE, Long.MAX_VALUE);
}

private boolean isValid(TreeNode node, long min, long max) {
    if (node == null) return true;
    if (node.val <= min || node.val >= max) return false;
    return isValid(node.left, min, node.val) 
           && isValid(node.right, node.val, max);
}

白话解释:给每个节点规定一个合法数值范围,检查它是否越界

2. BST中第K小的元素

解题思路

  • 中序遍历BST得到升序序列
  • 找第k个元素
java 复制代码
public int kthSmallest(TreeNode root, int k) {
    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();
        if (--k == 0) return curr.val;
        curr = curr.right;
    }
    return -1;
}

白话解释:BST的中序遍历就是按大小排队,直接数到第k个就行

四、树的遍历花样玩法

1. 层序遍历(按层打印)

解题思路

  • 用队列实现
  • 每次处理一层
java 复制代码
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;
}

白话解释:像点名一样,先点第一排的,记下来;再点他们的孩子,记下来...

2. Zigzag层序遍历

问题:一层从左到右,下一层从右到左

解题思路

  • 还是层序遍历
  • 加个标志位决定是否反转当前层
java 复制代码
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
    List<List<Integer>> result = new ArrayList<>();
    if (root == null) return result;
    
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);
    boolean reverse = false;
    
    while (!queue.isEmpty()) {
        int size = queue.size();
        List<Integer> level = new ArrayList<>();
        
        for (int i = 0; i < size; i++) {
            TreeNode node = queue.poll();
            level.add(node.val);
            if (node.left != null) queue.offer(node.left);
            if (node.right != null) queue.offer(node.right);
        }
        
        if (reverse) Collections.reverse(level);
        result.add(level);
        reverse = !reverse;
    }
    return result;
}

白话解释:正常点名,但记名字的时候隔一行就把名单倒过来写

五、实战技巧

  1. 模板化思考:很多树题都有固定套路,掌握几个模板就能解决大部分问题
  2. 画图辅助:在纸上画出小规模的树,手动模拟算法过程
  3. 边界检查:总是考虑空树、单节点树、左斜树、右斜树等特殊情况
  4. 空间优化:有些问题可以用Morris遍历实现O(1)空间复杂度
  5. 实战练习
    • LeetCode 94:二叉树的中序遍历
    • LeetCode 101:对称二叉树
    • LeetCode 104:二叉树的最大深度
    • LeetCode 105:从前序与中序遍历序列构造二叉树
    • LeetCode 236:二叉树的最近公共祖先

记住,树题大部分都是"纸老虎",只要掌握递归思想和几种遍历方式,多加练习就能轻松应对!

相关推荐
火柴就是我2 分钟前
git rebase -i 修改某次提交的message
android
zhangphil13 分钟前
Android ExifInterface rotationDegrees图旋转角度,Kotlin
android·kotlin
火柴就是我17 分钟前
需求开发提交了几个 commit,提交 master 领导 review 后,说你第一笔 commit 代码有问题,让你改一下,怎么办?
android
KdanMin1 小时前
Android系统通知机制深度解析:Framework至SystemUI全链路剖析
android
Wgllss3 小时前
Android下载进度百分比按钮,Compose轻松秒杀实现
android·架构·android jetpack
顾林海5 小时前
深度解析LinkedHashMap工作原理
android·java·面试
JasonYin5 小时前
Git提交前缀
android
louisgeek5 小时前
Android 类加载机制
android
碎风,蹙颦6 小时前
Android开发过程中遇到的SELINUX权限问题
android·人工智能
HZW89706 小时前
鸿蒙应用开发—数据持久化之SQLite
android·前端·harmonyos