day17 代码随想录算法训练营 二叉树专题5

1 今日题

最大二叉树 654. 最大二叉树 - 力扣(LeetCode)

合并二叉树 617. 合并二叉树 - 力扣(LeetCode)

二叉搜索树中的搜索 700. 二叉搜索树中的搜索 - 力扣(LeetCode)

验证二叉搜索树 98. 验证二叉搜索树 - 力扣(LeetCode)

2 最大二叉树

2.1 思路

构建最大二叉树的核心是递归分治思想:将数组视为一个整体,先找到当前区间的最大值作为根节点,再把最大值左侧的子数组递归构建左子树、右侧子数组递归构建右子树,直到区间无元素时终止递归。采用「左闭右闭」区间规则([leftIdx, rightIdx]),边界处理更符合直观思维,核心步骤可总结为:
终止条件:区间无元素(leftIdx > rightIdx)返回 null;区间仅 1 个元素(leftIdx == rightIdx)直接返回叶子节点;
找最大值:遍历当前区间,找到最大值及其索引,作为当前层的根节点;
递归构建:以最大值索引为分界,左侧区间构建左子树、右侧区间构建右子树;
返回节点:将当前层构建的节点返回,作为上层节点的左 / 右子节点。

时间复杂度:O(n^2),最坏情况(数组有序)下,每层递归遍历 O(n) 元素,共递归 n 层;
空间复杂度:O(n),递归栈深度最坏为 n(数组有序时退化为链表)。

2.2 实现代码

class Solution {
    // 对外暴露的入口方法,初始化左闭右闭区间:[0, nums.length-1]
    public TreeNode constructMaximumBinaryTree(int[] nums) {
        return traversal(nums, 0, nums.length - 1);
    }

    /**
     * 递归构建最大二叉树的核心方法
     * @param nums 原始数组
     * @param leftIdx 区间左边界(包含)
     * @param rightIdx 区间右边界(包含)
     * @return 当前区间构建的二叉树根节点
     */
    private TreeNode traversal(int[] nums, int leftIdx, int rightIdx) {
        // 递归终止条件1:区间无元素(左边界 > 右边界),返回null
        if (leftIdx > rightIdx) {
            return null;
        }
        // 递归终止条件2:区间仅1个元素(左边界 = 右边界),直接返回叶子节点
        if (leftIdx == rightIdx) {
            return new TreeNode(nums[leftIdx]);
        }

        // 步骤1:遍历当前区间,找到最大值及其索引
        int maxVal = -1;   // 初始化最大值(题目中nums元素为整数,初始值可设为极小值)
        int maxIdx = 0;    // 初始化最大值索引
        for (int i = leftIdx; i <= rightIdx; i++) { // 左闭右闭,循环到rightIdx(包含)
            if (nums[i] > maxVal) {
                maxVal = nums[i]; // 更新最大值
                maxIdx = i;       // 更新最大值索引
            }
        }

        // 步骤2:以最大值为当前层的根节点
        TreeNode root = new TreeNode(maxVal);

        // 步骤3:递归构建左子树 - 左区间[leftIdx, maxIdx-1](最大值左侧)
        root.left = traversal(nums, leftIdx, maxIdx - 1);
        // 步骤4:递归构建右子树 - 右区间[maxIdx+1, rightIdx](最大值右侧)
        root.right = traversal(nums, maxIdx + 1, rightIdx);

        // 返回当前层构建的节点,作为上层节点的左/右子节点
        return root;
    }
}

3 合并二叉树

3.1 思路

若两个节点都存在,则新节点值为两个节点值的和;
若其中一个节点不存在,则直接复用另一个节点(无需新建);
递归处理两个树的左子树、右子树,最终拼接成新的合并树。
 

3.2 实现代码

class Solution {
    /**
     * 合并两个二叉树的核心方法
     * @param root1 第一个二叉树的根节点
     * @param root2 第二个二叉树的根节点
     * @return 合并后的新二叉树根节点
     */
    public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
        // 递归终止条件1:如果root1为空,直接返回root2(复用root2的子树)
        if (root1 == null) {
            return root2;
        }
        // 递归终止条件2:如果root2为空,直接返回root1(复用root1的子树)
        if (root2 == null) {
            return root1;
        }

        // 核心逻辑:两个节点都存在时,新建节点,值为两者之和
        TreeNode root = new TreeNode(root1.val + root2.val);

        // 递归合并左子树:同步处理两个树的左孩子
        root.left = mergeTrees(root1.left, root2.left);
        // 递归合并右子树:同步处理两个树的右孩子
        root.right = mergeTrees(root1.right, root2.right);

        // 返回当前层合并后的节点,作为上层节点的左/右子节点
        return root;
    }
}

4 二叉搜索树中的搜索

4.1 迭代法思路

从根节点开始,逐个比较当前节点值与目标值;
如果当前节点值 > 目标值 → 目标值在左子树,向左移动;
如果当前节点值 < 目标值 → 目标值在右子树,向右移动;
如果相等 → 找到目标节点,直接返回;
如果遍历到空节点(cur == null)→ 目标值不存在,返回 null。

4.2 迭代法实现代码

class Solution {
    /**
     * 在二叉搜索树中查找值为val的节点(迭代实现)
     * @param root 二叉搜索树的根节点
     * @param val 要查找的目标值
     * @return 找到则返回对应节点,未找到返回null
     */
    public TreeNode searchBST(TreeNode root, int val) {
        // 初始化当前遍历节点为根节点
        TreeNode cur = root;
        
        // 迭代终止条件:当前节点为空(遍历完未找到)
        while (cur != null) {
            // 情况1:当前节点值 > 目标值 → 目标在左子树,向左移动
            if (cur.val > val) {
                cur = cur.left;
            } 
            // 情况2:当前节点值 < 目标值 → 目标在右子树,向右移动
            else if (cur.val < val) {
                cur = cur.right;
            } 
            // 情况3:值相等 → 找到目标节点,直接返回
            else {
                return cur;
            }
        }
        
        // 遍历完所有可能节点仍未找到,返回null
        return null;
    }
}

4.3 递归法思路

递归终止条件:
若当前节点为空(root == null),说明没找到目标值,返回 null;
若当前节点值等于目标值(root.val == val),找到目标节点,直接返回该节点;
递归分支逻辑:
若当前节点值 > 目标值 → 目标值只可能在左子树,递归查找左子树;
若当前节点值 < 目标值 → 目标值只可能在右子树,递归查找右子树;
最终兜底返回 null(逻辑上不会执行到,仅为语法完整性)。

4.4 递归法实现代码

class Solution {
    /**
     * 递归实现二叉搜索树(BST)的指定值查找
     * @param root BST的根节点
     * @param val 要查找的目标值
     * @return 找到则返回对应节点,未找到返回null
     */
    public TreeNode searchBST(TreeNode root, int val) {
        // 递归终止条件(核心):
        // 1. 当前节点为空 → 遍历到叶子节点的子节点,未找到目标值,返回null;
        // 2. 当前节点值等于目标值 → 找到目标节点,直接返回该节点;
        if (root == null || root.val == val) {
            return root;
        }

        // 分支1:当前节点值 > 目标值 → 目标值在左子树,递归查找左子树
        if (root.val > val) {
            return searchBST(root.left, val);
        }

        // 分支2:当前节点值 < 目标值 → 目标值在右子树,递归查找右子树
        if (root.val < val) {
            return searchBST(root.right, val);
        }

        // 兜底返回(逻辑上不会执行到,因为上面的条件已覆盖所有情况)
        return null;
    }
}

模拟:


递归执行步骤(逐层拆解):
第一层调用:searchBST(4, 3) → 4 > 3 → 递归调用searchBST(2, 3);
第二层调用:searchBST(2, 3) → 2 < 3 → 递归调用searchBST(3, 3);
第三层调用:searchBST(3, 3) → 3 == 3 → 触发终止条件,返回节点 3;
结果回传:第三层返回的节点 3 → 第二层返回节点 3 → 第一层返回节点 3;
最终返回目标节点 3。
再看查找目标值5的流程:
第一层:searchBST(4, 5) → 4 < 5 → 调用searchBST(7, 5);
第二层:searchBST(7, 5) → 7 > 5 → 调用searchBST(null, 5);
第三层:searchBST(null, 5) → root==null → 返回 null;
结果回传:第三层 null → 第二层 null → 第一层 null;
最终返回 null。

5 验证二叉搜索树

5.1 方法一思路

用数组来存中序遍历结果。遍历完再遍历数组看是不是严格递增的。

思路很简单但是空间复杂度时间复杂度都很高。效率低下。

5.2 方法一实现代码

class Solution {
    // 用来存中序遍历的结果
    private List<Integer> inorderList = new ArrayList<>();

    public boolean isValidBST(TreeNode root) {
        // 第一步:中序遍历,把节点值存入数组
        inorderTraversal(root);
        
        // 第二步:检查数组是否严格递增
        for (int i = 1; i < inorderList.size(); i++) {
            // 如果当前值 <= 前一个值,说明不是BST
            if (inorderList.get(i) <= inorderList.get(i-1)) {
                return false;
            }
        }
        // 数组严格递增,是合法BST
        return true;
    }

    // 普通的中序遍历(左→中→右),把值存入list
    private void inorderTraversal(TreeNode root) {
        if (root == null) {
            return;
        }
        // 左
        inorderTraversal(root.left);
        // 中:把当前节点值加入数组
        inorderList.add(root.val);
        // 右
        inorderTraversal(root.right);
    }
}

5.3 方法二思路

递归遍历规则:采用「中序遍历」(左 → 中 → 右)的递归顺序,保证遍历顺序符合 BST 的递增要求;
前驱节点记录:用一个全局变量pre 记录中序遍历的「上一个节点(前驱节点)」,跨递归层级保存状态;
实时验证逻辑:遍历到当前节点时,若pre不为空且当前节点值 ≤ pre值,说明违反「严格递增」,直接判定为非法 BST;若合法则更新pre为当前节点,继续验证右子树。

递归流程:先逐层调用左子树(压栈)→ 空节点返回 true → 处理当前节点(验证 pre)→ 更新 pre → 调用右子树 → 逐层返回(弹栈);
剪枝逻辑:只要某一层返回 false,上层会直接返回 false,无需继续遍历,效率高;
非法判断:只要当前节点≤pre,立刻返回 false,这是验证 BST 的核心。

5.4 方法二实现代码

class Solution {
    // 改名!pre = 前驱节点(中序遍历的上一个节点),这才是直观的命名
    TreeNode pre;

    public boolean isValidBST(TreeNode root) {
        // 递归终止条件:空节点合法
        if (root == null) {
            return true;
        }

        // 1. 中序遍历:先验证左子树
        boolean leftValid = isValidBST(root.left);
        if (!leftValid) { // 左子树非法,直接返回false(剪枝)
            return false;
        }

        // 2. 中序遍历:处理当前节点(核心验证)
        if (pre != null && root.val <= pre.val) { // 前驱节点存在,且当前节点≤前驱 → 非法
            return false;
        }
        pre = root; // 更新前驱节点为当前节点

        // 3. 中序遍历:验证右子树
        boolean rightValid = isValidBST(root.right);
        return rightValid;
    }
}

模拟:

阶段 1:逐层深入左子树(调用阶段)
主程序调用 isValidBST(6)(根节点),递归栈压入 isValidBST(6);
节点 6 的左子树未验证,调用 isValidBST(4),栈压入 isValidBST(4);
节点 4 的左子树未验证,调用 isValidBST(3),栈压入 isValidBST(3);
节点 3 的左子树未验证,调用 isValidBST(1),栈压入 isValidBST(1);
节点 1 的左子树为空,调用 isValidBST(null),返回 true(空节点合法)。
阶段 2:左子树验证 + pre 更新(返回阶段)
处理节点 1:pre=null(无前置节点),验证合法,更新 pre=1;
节点 1 的右子树为空,调用 isValidBST(null) 返回 true;
isValidBST(1) 执行完毕,栈弹出该方法,返回 true 给上层 isValidBST(3);
处理节点 3:3>pre(1),验证合法,更新 pre=3;
节点 3 的右子树为空,调用 isValidBST(null) 返回 true;
isValidBST(3) 执行完毕,栈弹出,返回 true 给上层 isValidBST(4);
处理节点 4:4>pre(3),验证合法,更新 pre=4,调用右子树 isValidBST(7)。
阶段 3:节点 7 验证(埋下非法隐患)
节点 7 的左子树为空,调用 isValidBST(null) 返回 true;
处理节点 7:7>pre(4),验证合法,更新 pre=7;
节点 7 的右子树为空,调用 isValidBST(null) 返回 true;
isValidBST(7) 执行完毕,栈弹出,返回 true 给上层 isValidBST(4);
isValidBST(4) 执行完毕,栈弹出,返回 true 给上层 isValidBST(6)。
阶段 4:触发非法 + 剪枝返回(核心终止)
处理根节点 6:此时 pre=7,判断 6≤7 → 触发非法条件;
无需调用节点 6 的右子树(isValidBST(8)),isValidBST(6) 直接返回 false;
递归栈弹出 isValidBST(6),整个验证流程终止,最终返回 false,判定树非法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

I_LPL

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值