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,判定树非法。

6887

被折叠的 条评论
为什么被折叠?



