深入浅出Java算法树结构
树结构算法题在面试中出现的频率特别高,今天我就用最通俗的大白话,给大家讲讲怎么搞定这些题目。
一、树题解题三板斧
遇到树的问题,先想这三招:
- 递归大法好:树天生适合递归,大部分问题都能用递归解决
- 迭代要用栈/队列:不想用递归?那就用栈或队列来模拟
- 分治思想:把大树拆成小树,分别解决再合并
二、高频考题解题套路
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;
}
白话解释:正常点名,但记名字的时候隔一行就把名单倒过来写
五、实战技巧
- 模板化思考:很多树题都有固定套路,掌握几个模板就能解决大部分问题
- 画图辅助:在纸上画出小规模的树,手动模拟算法过程
- 边界检查:总是考虑空树、单节点树、左斜树、右斜树等特殊情况
- 空间优化:有些问题可以用Morris遍历实现O(1)空间复杂度
- 实战练习 :
- LeetCode 94:二叉树的中序遍历
- LeetCode 101:对称二叉树
- LeetCode 104:二叉树的最大深度
- LeetCode 105:从前序与中序遍历序列构造二叉树
- LeetCode 236:二叉树的最近公共祖先
记住,树题大部分都是"纸老虎",只要掌握递归思想和几种遍历方式,多加练习就能轻松应对!