力扣top100(day03-01)--二叉树 03

本文为力扣TOP100刷题笔记

笔者根据数据结构理论加上最近刷题整理了一套 数据结构理论加常用方法以下为该文章:

力扣外传之数据结构(一篇文章搞定数据结构)

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 boolean isValidBST(TreeNode root) {
        return treebst(root,Long.MIN_VALUE,Long.MAX_VALUE);
    }
    public boolean treebst(TreeNode tree,long lower, long upper){
        if (tree == null) {
            return true;
        }
        if(tree.val<=lower || tree.val>=upper){
            return false;
        }
        return treebst(tree.left,lower,tree.val)&&treebst(tree.right,tree.val,upper);
    }
}

方法结构

  1. 主方法 isValidBST:

    • 接受一个 TreeNode 作为参数,表示二叉树的根节点

    • 调用辅助方法 treebst 进行实际验证

    • 使用 Long.MIN_VALUELong.MAX_VALUE 作为初始的上下界

  2. 辅助方法 treebst:

    • 参数:当前节点 tree,当前允许的最小值 lower,当前允许的最大值 upper

    • 递归地验证每个子树

算法逻辑

  1. 基本情况:

    • 如果当前节点为 null,返回 true(空树是有效的BST)
  2. 验证当前节点:

    • 检查当前节点的值是否在允许的范围内

    • 如果 tree.val <= lowertree.val >= upper,返回 false

    • 这确保了BST的性质:左子树所有节点必须小于当前节点,右子树所有节点必须大于当前节点

  3. 递归验证子树:

    • 对于左子树:上限变为当前节点的值,下限保持不变

    • 对于右子树:下限变为当前节点的值,上限保持不变

    • 只有当左右子树都有效时,当前树才有效

关键点

  • 使用长整型边界 :防止节点值等于 Integer.MIN_VALUEInteger.MAX_VALUE 时出现边界问题

  • 递归传递边界:在递归过程中不断缩小允许的值范围

  • 前序遍历:先检查当前节点,再递归检查左右子树

时间复杂度

  • O(n):需要访问树中的每个节点一次

  • 空间复杂度:O(h),其中h是树的高度(递归调用栈的深度)

这个算法高效地验证了BST的性质,确保左子树所有节点小于父节点,右子树所有节点大于父节点。

230. 二叉搜索树中第 K 小的元素

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 kthSmallest(TreeNode root, int k) {
    // 使用双端队列作为栈来存储节点
    Deque<TreeNode> stack = new ArrayDeque<TreeNode>();
    
    // 循环条件:当前节点不为空或栈不为空
    while (root != null || !stack.isEmpty()) {
        // 一直向左遍历,将所有左节点压入栈
        while (root != null) {
            stack.push(root);
            root = root.left;
        }
        
        // 弹出栈顶节点(当前最小节点)
        root = stack.pop();
        
        // 计数器递减
        --k;
        
        // 如果k减到0,说明找到了第k小的元素
        if (k == 0) {
            break;
        }
        
        // 转向右子树
        root = root.right;
    }
    
    // 返回当前节点的值(此时k=0)
    return root.val;
    }
}

关键点解释

  1. 栈的使用

    • 使用Deque作为栈来模拟递归调用的系统栈

    • 每次将左子节点压入栈,直到最左端

  2. 中序遍历过程

    • 外层while循环控制整体遍历

    • 内层while循环负责一直向左遍历

    • pop()操作对应访问当前最小节点

    • root = root.right转向右子树

  3. k的处理

    • 每访问一个节点,k减1

    • 当k减到0时,当前节点就是第k小的元素

示例演示

考虑BST:

text

复制代码
      5
     / \
    3   6
   / \
  2   4
 /
1

查找第3小的元素:

  1. 栈:[5,3,2,1],访问1,k=2

  2. 弹出1,访问2,k=1

  3. 弹出2,访问3,k=0 → 找到第3小的元素3

199. 二叉树的右视图

java 复制代码
public List<Integer> rightSideView(TreeNode root) {
    // 存储每个深度对应的最右侧节点值
    Map<Integer, Integer> rightmostValueAtDepth = new HashMap<Integer, Integer>();
    // 记录最大深度
    int max_depth = -1;

    // 使用两个栈分别存储节点和对应的深度
    Deque<TreeNode> nodeStack = new LinkedList<TreeNode>();
    Deque<Integer> depthStack = new LinkedList<Integer>();
    
    // 初始化栈
    nodeStack.push(root);
    depthStack.push(0);

    while (!nodeStack.isEmpty()) {
        TreeNode node = nodeStack.pop();
        int depth = depthStack.pop();

        if (node != null) {
            // 更新最大深度
            max_depth = Math.max(max_depth, depth);

            // 只有当该深度尚未记录时才存入(保证记录的是该深度最后访问的节点)
            if (!rightmostValueAtDepth.containsKey(depth)) {
                rightmostValueAtDepth.put(depth, node.val);
            }

            // 注意压栈顺序:先左后右,出栈时就是先右后左
            nodeStack.push(node.left);
            nodeStack.push(node.right);
            depthStack.push(depth + 1);
            depthStack.push(depth + 1);
        }
    }

    // 按照深度顺序构建结果列表
    List<Integer> rightView = new ArrayList<Integer>();
    for (int depth = 0; depth <= max_depth; depth++) {
        rightView.add(rightmostValueAtDepth.get(depth));
    }

    return rightView;
}

关键点解释

  1. 双栈结构

    • nodeStack存储待访问的节点

    • depthStack存储对应节点的深度

    • 两个栈同步操作,保证节点和深度对应

  2. 右视图记录策略

    • 使用HashMap按深度记录节点值

    • 每个深度只记录第一个访问到的节点(由于栈是后进先出,先压左节点后压右节点,所以右节点会先被访问)

  3. 遍历顺序

    • 虽然是DFS,但通过栈的压入顺序实现了"右节点优先"的访问顺序

    • 先压左子节点,后压右子节点,保证右子节点先出栈被访问

示例演示

考虑二叉树:

text

复制代码
      1
     / \
    2   3
     \   \
      5   4

右视图应为[1, 3, 4]

执行过程:

  1. 深度0:记录1

  2. 深度1:先访问3(右节点),再访问2(但深度1已记录)

  3. 深度2:先访问4(右节点),再访问5(但深度2已记录)

114. 二叉树展开为链表

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 void flatten(TreeNode root) {
        // 存储前序遍历结果的列表
        List<TreeNode> list = new ArrayList<TreeNode>();
        // 执行前序遍历,填充列表
        preorderTraversal(root, list);
        
        int size = list.size();
        // 将列表中的节点连接成链表
        for (int i = 1; i < size; i++) {
            TreeNode prev = list.get(i - 1); // 前一个节点
            TreeNode curr = list.get(i);    // 当前节点
            prev.left = null;              // 左指针置空
            prev.right = curr;             // 右指针指向当前节点
        }
    }

    // 递归前序遍历辅助方法
    public void preorderTraversal(TreeNode root, List<TreeNode> list) {
        if (root != null) {
            list.add(root);                     // 访问当前节点
            preorderTraversal(root.left, list);  // 遍历左子树
            preorderTraversal(root.right, list); // 遍历右子树
        }
    }
}

关键点解释

  1. 前序遍历收集节点

    • 使用递归前序遍历(根-左-右顺序)将所有节点按顺序存入列表

    • 列表中的节点顺序就是最终链表的顺序

  2. 链表重构

    • 遍历节点列表,将每个节点的左指针置为null

    • 将前一个节点的右指针指向当前节点

    • 最终形成只有右指针的单链表结构

  3. 原地修改

    • 直接修改原二叉树的结构,不创建新节点

    • 所有操作都在原节点上进行

示例演示

给定二叉树:

text

复制代码
    1
   / \
  2   5
 / \   \
3   4   6

执行过程:

  1. 前序遍历结果列表:[1,2,3,4,5,6]

  2. 重构链表:

    • 1.right=2, 1.left=null

    • 2.right=3, 2.left=null

    • 3.right=4, 3.left=null

    • ...

    • 最终链表:1→2→3→4→5→6

105. 从前序与中序遍历序列构造二叉树

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 {
    private Map<Integer, Integer> indexMap;

    public TreeNode myBuildTree(int[] preorder, int[] inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
        if (preorder_left > preorder_right) {
            return null;
        }

        // 前序遍历中的第一个节点就是根节点
        int preorder_root = preorder_left;
        // 在中序遍历中定位根节点
        int inorder_root = indexMap.get(preorder[preorder_root]);
        
        // 先把根节点建立出来
        TreeNode root = new TreeNode(preorder[preorder_root]);
        // 得到左子树中的节点数目
        int size_left_subtree = inorder_root - inorder_left;
        // 递归地构造左子树,并连接到根节点
        // 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
        root.left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
        // 递归地构造右子树,并连接到根节点
        // 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
        root.right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
        return root;
    }

    public TreeNode buildTree(int[] preorder, int[] inorder) {
        int n = preorder.length;
        // 构造哈希映射,帮助我们快速定位根节点
        indexMap = new HashMap<Integer, Integer>();
        for (int i = 0; i < n; i++) {
            indexMap.put(inorder[i], i);
        }
        return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1);
    }
}

关键点解释

  1. 哈希映射优化

    • 预处理中序遍历数组,建立值到索引的映射

    • 使得在中序遍历中查找根节点位置的时间降为O(1)

  2. 递归构建过程

    • 前序遍历的第一个元素总是当前子树的根节点

    • 在中序遍历中定位该根节点,左侧为左子树,右侧为右子树

    • 根据左子树节点数量确定前序遍历中左右子树的分界

  3. 索引范围计算

    • 前序遍历左子树范围:[pre_left+1, pre_left+left_size]

    • 前序遍历右子树范围:[pre_left+left_size+1, pre_right]

    • 中序遍历左子树范围:[in_left, in_root-1]

    • 中序遍历右子树范围:[in_root+1, in_right]

示例演示

给定:

  • 前序遍历 preorder = [3,9,20,15,7]

  • 中序遍历 inorder = [9,3,15,20,7]

构建过程:

  1. 根节点3(前序第一个)

  2. 在中序中找到3,左侧[9]是左子树,右侧[15,20,7]是右子树

  3. 递归构建:

    • 左子树:前序[9],中序[9]

    • 右子树:前序[20,15,7],中序[15,20,7]

  4. 最终树结构:

text

复制代码
    3
   / \
  9  20
    /  \
   15   7

437. 路径总和 III

java 复制代码
class Solution {
    // 主方法:计算所有路径和等于targetSum的数量
    public int pathSum(TreeNode root, long targetSum) {
        if (root == null) {
            return 0;
        }

        // 计算以当前节点为起点的路径数量
        int ret = rootSum(root, targetSum);
        // 递归计算左子树的所有可能路径
        ret += pathSum(root.left, targetSum);
        // 递归计算右子树的所有可能路径
        ret += pathSum(root.right, targetSum);
        return ret;
    }

    // 辅助方法:计算以当前节点为起点的路径和等于targetSum的数量
    public int rootSum(TreeNode root, long targetSum) {
        int ret = 0;

        if (root == null) {
            return 0;
        }
        
        int val = root.val;
        // 如果当前节点值等于剩余目标值,找到一条路径
        if (val == targetSum) {
            ret++;
        } 

        // 递归检查左子树,更新剩余目标值
        ret += rootSum(root.left, targetSum - val);
        // 递归检查右子树,更新剩余目标值
        ret += rootSum(root.right, targetSum - val);
        return ret;
    }
}

关键点解释

  1. 双重递归结构

    • pathSum():遍历树的所有节点,将每个节点作为路径起点

    • rootSum():计算以给定节点为起点的所有满足条件的路径数量

  2. 路径计算逻辑

    • 每当路径和等于目标值时计数器加1

    • 继续向下搜索,因为可能有正负值抵消的情况(如路径1→-1→1的和为1)

  3. 递归终止条件

    • 当前节点为null时返回0

    • 不要求路径结束于叶子节点

示例演示

给定二叉树:

text

复制代码
      10
     /  \
    5   -3
   / \    \
  3   2   11
 / \   \
3  -2   1

目标值:8

执行过程:

  1. 从10开始:10→5→3(和为18,不等于8),继续向下...

  2. 从5开始:5→3(和为8,计数+1),5→3→-2(和为6),5→2→1(和为8,计数+1)

  3. 从3开始:3→3(和为6),3→-2(和为1)

  4. 从-3开始:-3→11(和为8,计数+1)

  5. 最终找到3条路径:[5,3]、[5,2,1]、[-3,11]

236. 二叉树的最近公共祖先

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

    private TreeNode ans;

    public Solution() {
        this.ans = null;
    }

    private boolean dfs(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null) return false;
        boolean lson = dfs(root.left, p, q);
        boolean rson = dfs(root.right, p, q);
        if ((lson && rson) || ((root.val == p.val || root.val == q.val) && (lson || rson))) {
            ans = root;
        } 
        return lson || rson || (root.val == p.val || root.val == q.val);
    }

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        this.dfs(root, p, q);
        return this.ans;
    }
}

关键点解释

  1. 递归终止条件

    • 当前节点为null时返回false

    • 表示当前路径不包含目标节点

  2. LCA判断条件

    • 情况1:左右子树分别包含p和q(lson && rson

    • 情况2:当前节点是p或q,且另一个节点在子树中

    • 满足任一条件即记录当前节点为LCA

  3. 返回值意义

    • 返回布尔值表示当前子树是否包含p或q

    • 用于向上传递节点存在信息

示例演示

给定二叉树:

text

复制代码
       3
     /   \
    5     1
   / \   / \
  6   2 0   8
     / \
    7   4

查找节点5和1的LCA:

  1. 递归到节点3时:

    • 左子树包含5(lson=true)

    • 右子树包含1(rson=true)

    • 满足lson && rson,记录3为LCA

查找节点5和4的LCA:

  1. 递归到节点5时:

    • 自身是5

    • 右子树包含4(rson=true)

    • 满足条件2,记录5为LCA

相关推荐
独行soc39 分钟前
2025年大模型安全岗的面试汇总(题目+回答)
android·人工智能·安全·面试·职场和发展·渗透测试
月殇_木言1 小时前
算法基础 第3章 数据结构
数据结构·算法
亮亮爱刷题2 小时前
算法提升之树上问题-(LCA)
数据结构·算法·leetcode·深度优先
岁忧2 小时前
(LeetCode 每日一题) 1780. 判断一个数字是否可以表示成三的幂的和 (数学、三进制数)
java·c++·算法·leetcode·职场和发展·go
浩少7023 小时前
LeetCode-16day:栈
java·数据结构·算法
胖咕噜的稞达鸭5 小时前
数据结构---关于复杂度的基础解析与梳理
c语言·数据结构·算法·leetcode
高山莫衣5 小时前
Polyak-Ruppert 平均
人工智能·算法·机器学习
秋难降6 小时前
【数据结构与算法】———链表归并排序的优势
python·算法·排序算法
用户30356298445747 小时前
LightRAG应用实践
人工智能·算法