Skip to content

二叉树高频题-下


普通二叉树 LCA

题目链接

https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/

思路分析


代码实现

java
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null || root == p || root == q) {
            // root 为空,或者遇到 p,或者遇到 q,返回 root
            return root;
        }

        TreeNode l = lowestCommonAncestor(root.left, p, q);
        TreeNode r = lowestCommonAncestor(root.right, p, q);

        // 情况二:左边搜到,右边也搜到,返回 root
        if (l != null && r != null) {
            return root;
        }
        if (l == null && r == null) {
            return null;
        }

        // 情况一:p,q 属于包含关系,在同一颗子树上
        //        只要遇到一个就返回
        // l,r 一个空,一个不为空,返回不空的那个
        return l != null ? l : r;
    }
}

搜索二叉树 LCA

题目链接

https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/

思路分析

本题需要利用搜索二叉树的性质,左子树所有节点值 <= 根节点 <= 右子树所有节点值

代码实现

java
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        // 搜索二叉树的性质:左子树节点值 < 根节点 < 右子树节点值
        // root 从上到下遍历,会有以下 4 种情况,假设 p < q
        // 1. p,q 属于同一颗子树,先遇到谁,谁就是答案
        // 2. root < p ~ q,root 往右移动
        // 3. p ~ q < root,root 往左移动
        // 4. p < root < q,root 就是答案

        // 先遇到谁返回谁,退出循环
        while (root.val != p.val && root.val != q.val) {
            // 情况二:p < root < q,root 就是答案,不用搜了,返回
            if (Math.min(p.val, q.val) < root.val && root.val < Math.max(p.val, q.val)) {
                break;
            }
            // 情况三、四
            root = root.val < Math.min(p.val, q.val) ? root.right : root.left;
        }
        // 情况一
        return root;
    }
}

累加和为 sum 的所有路径

题目链接

https://leetcode.cn/problems/path-sum-ii/description/

思路分析

典型的递归 + 回溯,递归过程中计算 curSum,并将值作为参数传递,如果符合条件就收集路径

本题要理解清楚递归的执行流程,回溯时,什么情况下要减去 curSum

代码实现

以下代码为更优雅的写法,把 res 和 path 作为递归方法的参数(或局部变量)传递,彻底避免成员变量的依赖,代码可复用性更高

java
/**
 * Definition for a binary tree node.
 * public 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;
 *     }
 * }
 */
class Solution {
    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        List<List<Integer>> res = new ArrayList<>();
        if (root == null) {
            return res;
        }
        List<Integer> path = new ArrayList<>();
        traversal(root, targetSum, 0, path, res);
        return res;
    }

    // curSum 表示 cur 节点上方路径的累加和
    // 这里传递的是 curSum + cur.val 的计算结果(新值),而非修改原变量
    // 左子树递归栈中,拿到的 curSum 是 S + val,但这只是局部副本
    // 当左子树递归结束,回到当前层时,上层的 curSum 仍然是原来的 S,没有被修改
    // 即回溯时无需对 curSum 进行修改
    public void traversal(TreeNode cur, int targetSum, int curSum, List<Integer> path, List<List<Integer>> res) {
        // 递归终止条件,遇到叶子节点,收集路径
        if (cur.left == null && cur.right == null) {
            if (curSum + cur.val == targetSum) {
                path.add(cur.val);
                res.add(new ArrayList<>(path));
                // 回溯
                path.remove(path.size() - 1);
            }
        } else {
            // 不是叶子节点,继续递归搜索
            path.add(cur.val);
            if (cur.left != null) {
                traversal(cur.left, targetSum, curSum + cur.val, path, res);
            }
            if (cur.right != null) {
                traversal(cur.right, targetSum, curSum + cur.val, path, res);
            }
            path.remove(path.size() - 1);
        }
    }
}

验证平衡二叉树

题目链接

https://leetcode.cn/problems/balanced-binary-tree

思路分析

平衡二叉树:每个节点的左右子树的高度差值的绝对值 <= 1

递归求解每个节点的子树高度,判断是否符合条件即可

代码实现

java
/**
 * Definition for a binary tree node.
 * public 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;
 *     }
 * }
 */
class Solution {
    public static boolean balance;

    public boolean isBalanced(TreeNode root) {
        // balance 是全局变量,所有递归过程共享
        // 所以每次判断开始时候设置为 true
        balance = true;
        height(root);
        return balance;
    }

    // 求以 cur 为头的这颗子树的高度
    public static int height(TreeNode cur) {
        // 如果不平衡,返回什么已经不重要了
        if (!balance || cur == null) {
            return 0;
        }

        // 递归求左右子树的高度
        int leftHeight = height(cur.left);
        int rightHeight = height(cur.right);

        // 判断是否是平衡二叉树
        if (Math.abs(leftHeight - rightHeight) > 1) {
            balance = false;
        }

        // 返回树高,树高 = 当前节点这层高度 + 左右子树的最大高度
        return Math.max(leftHeight, rightHeight) + 1;
    }
}

判断搜索二叉树

题目链接

https://leetcode.cn/problems/validate-binary-search-tree/

思路分析

搜索二叉树:对于每一颗子树,都应该满足左子树所有节点都小于根节点,根节点小于右子树的所有节点

方法一

非递归实现中序遍历,判断是否是升序序列即可

java
/**
 * Definition for a binary tree node.
 * public 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;
 *     }
 * }
 */
class Solution {

    public static int MAXN = 10001;

    public static TreeNode[] stack = new TreeNode[MAXN];

    public static int size;

    public boolean isValidBST(TreeNode root) {
        if (root == null) {
            return true;
        }
        // 定义前驱指针,用于比较,判断是否升序
        TreeNode pre = null;
        // size 始终指向栈顶元素的下一个位置
        size = 0;
        while (size > 0 || root != null) {
            // 一直往左
            if (root != null) {
                stack[size++] = root;
                root = root.left;
            } else {
                // 弹出栈顶元素,处理右子树
                root = stack[--size];
                // 利用升序特性,判断是否搜索二叉树
                if (pre != null && pre.val >= root.val) {
                    return false;
                }
                // 移动前驱指针
                pre = root;
                root = root.right;
            }
        }
        return true;
    }
}

方法二

利用搜索二叉树的性质,左子树的 max < 根节点 < 右子树的 min

每棵子树的左子树,右子树的最大和最小分别用局部变量接收,将信息返回给子树的根节点

用全局变量 min,max 记录整棵子树的最大和最小,更新全局变量,并将信息向上返回


java
/**
 * Definition for a binary tree node.
 * public 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;
 *     }
 * }
 */
class Solution {

    public static long min;

    public static long max;

    public boolean isValidBST(TreeNode root) {
        // 左子树的 max < 根节点 < 右子树的 min
        if (root == null) {
            // 这样设置的目的是不让空节点影响判断
            // 最小值为最大,最大值为最小
            min = Long.MAX_VALUE;
            max = Long.MIN_VALUE;
            // 空节点符合条件
            return true;
        }

        boolean leftIsValid = isValidBST(root.left);
        // 左树的最小和最大
        long leftMin = min;
        long leftMax = max;

        boolean rightIsValid = isValidBST(root.right);
        // 右树最小和最大
        long rightMin = min;
        long rightMax = max;

        // 更新当前子树的最小和最大
        // 将该信息返回给上游节点
        min = Math.min(Math.min(leftMin, rightMin), root.val);
        max = Math.max(Math.max(leftMax, rightMax), root.val);

        return leftIsValid && rightIsValid && leftMax < root.val && root.val < rightMin;
    }
}

修建搜索二叉树

题目链接

https://leetcode.cn/problems/trim-a-binary-search-tree/

题意:给定范围 [low, high],将二叉树修剪成所有节点都在这个范围内的二叉树

思路分析

利用搜索二叉树的性质,左子树的 max < 根节点 < 右子树的 min

如果 root.val < low,则说明 root 及其左子树的节点都是 < low 的,递归修剪右子树

如果 root.val > high,则说明 root 及其右子树的节点都是 > high 的,递归修剪左子树

否则 root 就是在 low,high 的范围内,递归修剪左右子树并链接,将节点返回即可

代码实现

java
/**
 * Definition for a binary tree node.
 * public 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;
 *     }
 * }
 */
class Solution {
    // 力扣是 TreeNode root,这里改成 cur 表示当前节点,便于理解
    // [left, right] 范围内,左闭右闭区间
    public TreeNode trimBST(TreeNode cur, int low, int high) {
        if (cur == null) {
            return null;
        }
        if (cur.val < low) {
            return trimBST(cur.right, low, high);
        }
        if (cur.val > high) {
            return trimBST(cur.left, low, high);
        }
        // left < cur < right
        cur.left = trimBST(cur.left, low, high);
        cur.right = trimBST(cur.right, low, high);
        return cur;
    }
}

二叉树打家劫舍问题

题目链接

https://leetcode.cn/problems/house-robber-iii/

思路分析

代码实现

java
/**
 * Definition for a binary tree node.
 * public 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;
 *     }
 * }
 */
class Solution {
    public int rob(TreeNode root) {
        trversal(root);
        // 偷头节点和不偷头节点两个情况取最大值
        return Math.max(yes, no);
    }

    // 全局变量,完成了 X 子树的遍历,返回之后
    // yes 变成 X 子树在偷头节点的情况下的最大收益
    public static int yes;

    // 全局变量,完成了 X 子树的遍历,返回之后
    // no 变成 X 子树在不偷头节点的情况下的最大收益
    public static int no;

    public void trversal(TreeNode root) {
        if (root == null) {
            yes = 0;
            no = 0;
        } else {
            // 初始变量
            // 偷头节点,初始为 root.val
            int y = root.val;
            // 不偷头节点,初始为 0
            int n = 0;

            // 递归左子树
            trversal(root.left);
            // 偷头节点,子树的头节点不能偷
            // 则加上不偷子树头节点的价值
            y += no;
            // 不偷头节点,那子树的头节点可偷可不偷
            // 取最大价值
            n += Math.max(yes, no);

            // 同理递归右子树
            trversal(root.right);
            y += no;
            n += Math.max(yes, no);

            // 更新全局变量,向上返回
            yes = y;
            no = n;
        }
    }
}