【LeetCode HOT 100】详细题解之二叉树篇

【LeetCode HOT 100】详细题解之二叉树篇

    • 94 二叉树的中序遍历
      • 方法一:递归
      • 方法二:迭代
    • 104 二叉树的最大深度
      • 方法一:递归
      • 方法二:迭代
    • 226 翻转二叉树
      • 方法一:递归
      • 方法二:迭代
    • 101 对称二叉树
      • 方法一:递归
        • 复杂度分析
        • 注意事项
      • 方法二:迭代
    • 543 二叉树的直径
      • 方法一:递归
        • 复杂度分析
        • 注意事项
    • 102 二叉树的层序遍历
      • 方法一:迭代
    • 108 将有序数组转换为二叉搜索树
      • 方法一:递归
    • 98 验证二叉搜索树
      • 方法一:递归
    • 230 二叉搜索树中第k小的元素
      • 方法一:递归
    • 199 二叉树的右视图
      • 方法一:层序遍历
    • 114 二叉树展开为链表
      • 方法一:递归
    • 105 从前序与中序遍历序列构造二叉树
      • 方法一:递归
        • 问题分析
        • 算法逻辑
    • 437 路径总和III
      • 方法一:递归+前缀和
        • 问题分析
        • 算法逻辑
    • 236 二叉树的最近公共祖先
      • 方法一:递归+回溯
    • 124 二叉树中的最大路径和
      • 方法一:递归

94 二叉树的中序遍历

94. 二叉树的中序遍历

给定一个二叉树的根节点 root ,返回 它的 中序 遍历

示例 1:
在这里插入图片描述

输入:root = [1,null,2,3]
输出:[1,3,2]

示例 2:

输入:root = []
输出:[]

示例 3:

输入:root = [1]
输出:[1]

提示:

  • 树中节点数目在范围 [0, 100]
  • -100 <= Node.val <= 100

进阶: 递归算法很简单,你可以通过迭代算法完成吗?

方法一:递归

注意,二叉树的中序遍历为左中右。因此递归时按该处理顺序处理即可。

/*** 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<Integer> inorderTraversal(TreeNode root) {List<Integer> res = new ArrayList<>();inorder(root,res);return res;}private void inorder(TreeNode root,List<Integer> res){if(root == null){return;}inorder(root.left,res);res.add(root.val);inorder(root.right,res);}
}

方法二:迭代

这里有点绕。定义一个访问节点来帮助迭代。

因为访问顺序和处理顺序不一致,访问是一路访问到最左子树才开始处理。因此在这里先定义一个访问的指针。

当指针不为空时,将访问的指针入栈,继续向左走,直到找到最左下面的节点。

找到后开始处理,先出栈,将出栈的节点的值放进res中,之后将指针修改为出栈节点的right,开始遍历右子树。

        // 栈 先进后出// 前序遍历,出栈顺序:根左右; 入栈顺序:右左根// 中序遍历,出栈顺序:左根右; 入栈顺序:右根左// 后序遍历,出栈顺序:左右根; 入栈顺序:根右左
/*** 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<Integer> inorderTraversal(TreeNode root) {List<Integer> res = new ArrayList<>();//中序遍历这里需要注意的是,由于访问元素和处理的元素不一样//所以写法不同于前序和后序,通过指针的遍历来访问结点,栈用来处理节点上的元素Deque<TreeNode> stack = new LinkedList<>();stack.push(root);TreeNode curr = root;//定义一个访问的指针while(!stack.isEmpty() && curr != null){//将访问的结点放进栈中if(curr != null){stack.push(curr);curr = curr.left;}else{//出栈curr = stack.pop();res.add(curr.val);//中curr = curr.right;//右}}return res;}}

104 二叉树的最大深度

104. 二叉树的最大深度

给定一个二叉树 root ,返回其最大深度。

二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。

示例 1:

在这里插入图片描述

输入:root = [3,9,20,null,null,15,7]
输出:3

示例 2:

输入:root = [1,null,2]
输出:2

提示:

  • 树中节点的数量在 [0, 104] 区间内。
  • -100 <= Node.val <= 100

方法一:递归

递归三要素为
1.确定递归函数的参数和返回类型
2.递归终止条件
3.递归的单层处理逻辑
本题中需要求出二叉树的最大深度,因此递归参数应为TreeNode节点,返回类型为当前节点的最大深度
递归终止条件为:遍历到叶子节点时返回,因此if(root == null) return 0;
单层处理逻辑:当前节点的最大深度 = Math.max(左子树的最大深度,右子树的最大深度)+1,左子树和右子树的最大深度又可以以同样的方式进行计算。
因此,函数的主要部分为:

        //根节点的深度为1,可以使用后序遍历,将左子树的深度和右子树的深度返回给根节点//1.递归终止条件if(root == null){return 0;}//2.递归的单层处理逻辑int LDepth = maxDepth(root.left);int RDepth = maxDepth(root.right);return Math.max(LDepth,RDepth) + 1;

最终代码如下:

/*** 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 maxDepth(TreeNode root) {//根节点的深度为1,可以使用后序遍历,将左子树的深度和右子树的深度返回给根节点if(root == null){return 0;}int LDepth = maxDepth(root.left);int RDepth = maxDepth(root.right);return Math.max(LDepth,RDepth) + 1;}
}

方法二:迭代

这里也可以用二叉树的层序遍历来解决。
层序遍历相较于递归来讲会更好理解。只需要统计遍历的最大层数,即为二叉树的最大深度.
在这里,将102 中层序遍历的解法稍微修改即可得到结果
代码如下

/*** 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 maxDepth(TreeNode root) {//方法二:层序遍历if(root == null){return 0;}Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);int depth = 0;while(!queue.isEmpty()){depth ++; //深度+1int currentSize = queue.size();for(int i = 0;i<currentSize;i++){TreeNode curr = queue.poll();if(curr.left != null) queue.offer(curr.left);if(curr.right != null) queue.offer(curr.right);}}return depth;}
}

226 翻转二叉树

226. 翻转二叉树

给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。

示例 1:

在这里插入图片描述

输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]

示例 2:
在这里插入图片描述

输入:root = [2,1,3]
输出:[2,3,1]

示例 3:

输入:root = []
输出:[]

提示:

  • 树中节点数目范围在 [0, 100]
  • -100 <= Node.val <= 100

方法一:递归

本题解采用递归的方法来实现二叉树的反转。递归的基本思想是:对于树中的每个节点,先递归地交换其左右子树,然后再交换当前节点的左右子树。

  1. 递归终止条件:如果当前节点为空,则返回空。这是因为空节点不需要交换,且在树的遍历过程中,空节点表示已经到达了树的叶子节点的子节点。

  2. 单层处理逻辑
    (对于当前非空节点,首先递归地对左子树和右子树进行反转。这一步保证了在处理当前节点之前,其左右子树已经被反转。)

    翻转左子树:root.left = invertTree(root.left);

    翻转右子树:root.right = invertTree(root.right);

    交换左右子树:在递归调用返回后,当前节点的左右子树已经被反转。此时,通过交换当前节点的左右子树,完成当前节点的反转。

        //交换当前节点的左右子树TreeNode temp = root.left;root.left = root.right;root.right = temp;
    
  3. 返回:返回当前节点root

最终代码如下:

/*** 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 TreeNode invertTree(TreeNode root) {//不停交换左右子树即可//1.递归终止条件  if(root == null){return null;}//2.单层处理逻辑root.left = invertTree(root.left);root.right = invertTree(root.right);//交换当前节点的左右子树TreeNode temp = root.left;root.left = root.right;root.right = temp;return root;}
}

复杂度分析
时间复杂度 O(N) : 其中 N 为二叉树的节点数量,建立二叉树镜像需要遍历树的所有节点,占用 O(N) 时间。
空间复杂度 O(N) : 最差情况下(当二叉树退化为链表),递归时系统需使用 O(N) 大小的栈空间。

方法二:迭代

采用层序遍历的方法来实现二叉树的反转。层序遍历是一种广度优先遍历方法,它按照从上到下、从左到右的顺序遍历树的每一层。

  1. 初始化队列:使用一个队列来存储待处理的节点。
  2. 遍历队列:只要队列不为空,就继续处理队列中的节点。
  3. 处理当前层的节点
    • 记录当前层的节点数量。
    • 遍历当前层的每个节点,对每个节点进行以下操作:
      • 如果节点有左子节点,将其加入队列。
      • 如果节点有右子节点,将其加入队列。
      • 交换当前节点的左右子树。
/*** 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 TreeNode invertTree(TreeNode root) {//方法二:迭代//层序遍历交换的思想是,将root的左右子树入队后,交换左右子树Queue<TreeNode> queue = new LinkedList<TreeNode>();if(root == null){return root;}queue.offer(root);while(!queue.isEmpty()){int currentSize = queue.size();for(int i = 0;i<currentSize;i++){TreeNode curr = queue.poll();//从队列中获取队首元素,并让队首元素出队if(curr.left!=null) queue.offer(curr.left);if(curr.right!=null) queue.offer(curr.right);//交换左右子树TreeNode temp = curr.left;curr.left = curr.right;curr.right = temp;}}return root;}
}

101 对称二叉树

101. 对称二叉树

给你一个二叉树的根节点 root , 检查它是否轴对称。

示例 1:

img

输入:root = [1,2,2,3,4,4,3]
输出:true

示例 2:

img

输入:root = [1,2,2,null,3,null,3]
输出:false

提示:

  • 树中节点数目在范围 [1, 1000]
  • -100 <= Node.val <= 100

**进阶:**你可以运用递归和迭代两种方法解决这个问题吗?

方法一:递归

为了判断二叉树是否对称,需要递归地比较每个节点的左子树和右子树。对于每个节点,需要满足以下两个条件:

  1. 左子树的左子节点和右子树的右子节点相等。
  2. 左子树的右子节点和右子树的左子节点相等。

递归的基本思想是:

  • 如果节点为空,返回 true,因为空树是对称的。
  • 如果左子节点不为空而右子节点为空,或者左子节点为空而右子节点不为空,返回 false
  • 如果左右子节点都不为空,但值不相等,返回 false
  • 如果左右子节点都不为空且值相等,递归地比较左子节点的左子树和右子树的右子树,以及左子节点的右子树和右子树的左子树。
/*** 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 isSymmetric(TreeNode root) {if(root == null){return true;}return compare(root.left,root.right);}private boolean compare(TreeNode left,TreeNode right){//方法一:递归//直接判断左子树是否等于右子树并不可行。而是要判断// 左子树的左子树是否等于右子树的右子树// 左子树的右子树是否等于右子树的左子树// 这样是因为,轴对称的话,要树的最左侧节点与最右侧节点相等(外侧)// 树的内侧节点相等// 比如在示例1中,我们要先判断最外侧 左边123 是否等于 右边123 // 之后再判断内侧 4 是否等于4 //1.递归终止条件//左右子树不等,直接返回falseif(left != null && right == null){return false;}if(left == null && right != null){return false;}if(left == null && right == null){return true;}if(left.val != right.val){return false;}//2.开始判断boolean outside = compare(left.left,right.right); //比较最外侧boolean inside = compare(left.right,right.left); //比较最内侧return inside && outside; }
}
复杂度分析
  • 时间复杂度:O(n),其中n为二叉树的节点数。每个节点恰好被访问一次。
  • 空间复杂度:O(h),其中h为二叉树的高度。这是因为递归过程中最多会有h层的递归调用,每层递归调用会占用一定的栈空间。
注意事项
  • 在比较节点值时,需要先检查节点是否为空,以避免空指针异常。
  • 递归算法需要确保递归终止条件正确,否则会导致无限递归。

方法二:迭代

首先我们引入一个队列,这是把递归程序改写成迭代程序的常用方法。初始化时我们把根节点入队两次。每次提取两个结点并比较它们的值(队列中每两个连续的结点应该是相等的,而且它们的子树互为镜像),然后将两个结点的左右子结点按相反的顺序插入队列中。当队列为空时,或者我们检测到树不对称(即从队列中取出两个不相等的连续结点)时,该算法结束。

/*** 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 isSymmetric(TreeNode root) {if(root == null){return true;}//方法二:迭代//此时的迭代并不是层序遍历,而是将我们要比较的元素成对的放入到队列中去进行比较// 每次都取出队列中的前两个元素比较//    1//  2    2//3 4   4 3 //第一次入队:push 1,1 // 进入while循环 取出队列中的前两个元素比较// 之后将两个结点的左右子节点按相反的顺序插入队列中,即插入a的左节点,b的右节点,a的右节点,b的左节点,实现比较Queue<TreeNode> queue = new LinkedList<TreeNode> ();queue.offer(root);queue.offer(root);while(!queue.isEmpty()){TreeNode root1 = queue.poll();TreeNode root2 = queue.poll();if( (root1 == null && root2 != null) || (root1 != null && root2 == null)){return false;}if(root1 == null && root2 == null){continue;}if(root1.val != root2.val){return false;}queue.offer(root1.left);  //比较外侧queue.offer(root2.right);queue.offer(root1.right); //比较内侧queue.offer(root2.left);}return true;}
}

543 二叉树的直径

543. 二叉树的直径

给你一棵二叉树的根节点,返回该树的 直径

二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root

两节点之间路径的 长度 由它们之间边数表示。

示例 1:

在这里插入图片描述

输入:root = [1,2,3,4,5]
输出:3
解释:3 ,取路径 [4,2,1,3] 或 [5,2,1,3] 的长度。

示例 2:

输入:root = [1,2]
输出:1

提示:

  • 树中节点数目在范围 [1, 104]
  • -100 <= Node.val <= 100

方法一:递归

这个问题可以通过递归求解。对于每个节点,我们可以计算它的左子树和右子树的深度。那么,通过这个节点的最长路径(直径)就是左子树的深度加上右子树的深度。

  1. 递归终止条件:如果节点为空,返回0。空节点没有深度。
  2. 计算深度:对于每个非空节点,递归地计算其左子树和右子树的深度。
  3. 更新直径:更新全局变量 ans,它保存了遍历过程中找到的最大直径。当前节点的直径是其左子树深度和右子树深度之和。
  4. 返回深度:返回节点的深度,这里我们返回的是子树中较大的深度加1,因为我们要计算的是以当前节点为根的子树的深度。
/*** 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 {//存储遍历过程中找到的最大直径int ans = Integer.MIN_VALUE;public int diameterOfBinaryTree(TreeNode root) {//递归求解,本题可以理解为求二叉树深度的变体。//对每个节点求其左子树的深度和右子树的深度,那么该节点的直径即为左子树深度+右子树深度-1//遍历每个节点,求出最大深度depth(root);return ans;}// 递归函数来计算节点的子树深度,并更新直径public int depth(TreeNode root){if(root == null){return 0;}int LHeight = depth(root.left);int RHeight = depth(root.right);// 更新ans为左子树深度与右子树深度之和的最大值// 这个和代表了通过当前节点的最长路径ans = Math.max(ans,LHeight + RHeight);// 返回子树中较大的深度加1,加1是因为也要计算当前节点的深度// 这样在下次递归时可以计算通过当前节点的路径长度return Math.max(LHeight,RHeight)+1;// 返回该层的深度}}
复杂度分析
  • 时间复杂度:O(n),其中n为二叉树的节点数。每个节点恰好被访问一次。
  • 空间复杂度:O(h),其中h为二叉树的高度。这是因为递归过程中最多会有h层的递归调用,每层递归调用会占用一定的栈空间。
注意事项
  • 直径可能不经过根节点,因此我们不能简单地使用二叉树的最大深度来求解。
  • 在更新直径时,需要考虑左右子树的深度之和。
  • 递归算法需要确保递归终止条件正确,否则会导致无限递归。

102 二叉树的层序遍历

102. 二叉树的层序遍历

给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

示例 1:
在这里插入图片描述

输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]

示例 2:

输入:root = [1]
输出:[[1]]

示例 3:

输入:root = []
输出:[]

提示:

  • 树中节点数目在范围 [0, 2000]
  • -1000 <= Node.val <= 1000

方法一:迭代

一开始想到了用队列,但是没有想到如何确认是哪一层的,后面发现可以先获取队列长度,通过这种方式来控制遍历的次数,从而取到某一层的结点。

其次就是注意根节点为空的特殊情况。

最后Queue队列的一些方法是poll和offer 一个出队一个入队

/*** 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>> levelOrder(TreeNode root) {List<List<Integer>> res = new ArrayList<List<Integer>>();if(root == null){return res;}Queue<TreeNode> queue = new LinkedList<>();queue.offer(root); //添加元素while(!queue.isEmpty()){int currentLevelSize = queue.size();List<Integer> level = new ArrayList<>();//取出当前层的节点for(int i = 0;i<currentLevelSize;i++){TreeNode node = queue.poll(); //出队列if(node != null){level.add(node.val);if(node.left!=null) queue.offer(node.left);if(node.right != null) queue.offer(node.right);}}res.add(level);}return res;}
}

108 将有序数组转换为二叉搜索树

108. 将有序数组转换为二叉搜索树

给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 平衡二叉搜索树。

示例 1:
在这里插入图片描述

输入:nums = [-10,-3,0,5,9]
输出:[0,-3,9,-10,null,5]
解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案:

示例 2:
在这里插入图片描述

输入:nums = [1,3]
输出:[3,1]
解释:[1,null,3] 和 [3,1] 都是高度平衡二叉搜索树。

提示:

  • 1 <= nums.length <= 104
  • -104 <= nums[i] <= 104
  • nums严格递增 顺序排列

方法一:递归

为了将一个有序数组转换为一棵高度平衡的BST,可以采用递归的方法。基本思路是利用数组的有序性,在数组中间找到根节点的值,然后递归地将数组的左半部分构造为左子树,将右半部分构造为右子树。

  1. 递归终止条件:如果数组的起始索引大于或等于结束索引,则当前没有元素可以构造子树,返回 null
  2. 找到根节点:计算中间索引 mid,将位于该索引的值作为当前子树的根节点。
  3. 构造左子树:递归地使用数组的左半部分(从起始索引到mid)来构造根节点的左子树。
  4. 构造右子树:递归地使用数组的右半部分(从mid + 1到结束索引)来构造根节点的右子树。
  5. 返回根节点:返回当前子树的根节点。
/*** 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 TreeNode sortedArrayToBST(int[] nums) {// 这种构建二叉树的题目,关键是要确认如何才能递归的构建左子树和右子树// 本题中,因为是有序的数组,所以我们只需要每次取出中间的元素作为根节点,左侧元素为左子树,右侧为右子树// 通过这种方式递归的构建即可return tranverse(nums,0,nums.length) ;//左闭右开}public TreeNode tranverse(int[] nums,int left,int right){//递归终止if(left >= right){return null;}//计算中间索引int mid = (left + right) /2;//创建当前子树的根节点TreeNode root = new TreeNode(nums[mid]);//递归构建左子树root.left = tranverse(nums,left,mid);//递归构建右子树root.right = tranverse(nums,mid+1,right);//返回当前子树的根节点return root;}
}

复杂度分析

  • 时间复杂度:O(n),其中n为数组的长度。每个元素恰好被访问一次。
  • 空间复杂度:O(log n),这是递归过程中栈的深度,由于树是平衡的,所以递归深度是树的高度,对于平衡BST,高度大约是log(n)。

注意事项

  • 确保递归的左右边界是正确的,左闭右开(left <= right)。
  • 计算中间索引时,使用 (left + right) / 2 可以避免在大数组中可能发生的整数溢出。

98 验证二叉搜索树

98. 验证二叉搜索树

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

有效 二叉搜索树定义如下:

  • 节点的左子树只包含 小于 当前节点的数。

  • 节点的右子树只包含 大于 当前节点的数。

  • 所有左子树和右子树自身必须也是二叉搜索树。

示例 1:
在这里插入图片描述

示例 2:

在这里插入图片描述

输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4 。

提示:

  • 树中节点数目范围在[1, 10^4]
  • -2^31 <= Node.val <= 2^31 - 1

方法一:递归

为了判断一个二叉树是否为BST,可以利用BST的性质:BST的中序遍历结果是一个递增的有序数组。因此,可以通过中序遍历二叉树,并检查遍历结果是否有序。

  1. 递归终止条件:如果当前节点为空,返回 true。空树是BST。
  2. 中序遍历:递归地进行左子树的判断,然后检查当前节点,最后递归地进行右子树的判断。
  3. 当前节点的前一个节点:使用一个全局变量 pre 来记录中序遍历中的前一个访问的节点。
  4. 节点值的比较:在访问当前节点时,如果 pre 不为空且当前节点的值不大于 pre 的值,则不是BST,返回 false
  5. 更新前一个节点:将当前节点设置为 pre,以便下一次递归时使用。
  6. 递归右子树:对右子树进行相同的判断。
  7. 组合左右子树的结果:当前节点的BST性质由左右子树的结果决定。
class Solution {// 记录当前节点的前一个节点TreeNode pre;public boolean isValidBST(TreeNode root) {// 二叉搜索树的性质为:中序遍历的结果一定是一个有序数组// 我们可以利用这一性质,通过中序遍历,遍历时记录当前节点的前一个节点// 判断前一个节点是否小于当前节点的值,若所有节点都满足返回true// 1.递归终止条件if (root == null) {return true;}// 2.中序遍历boolean left = isValidBST(root.left);if (pre != null && pre.val >= root.val) {return false; // 如果前一个节点的值大于或等于当前节点的值,则不是BST}pre = root; // 更新前一个节点为当前节点boolean right = isValidBST(root.right);return left && right; // 左右子树均满足BST性质时,当前树才满足BST性质}
}

复杂度分析

  • 时间复杂度:O(n),其中n为二叉树的节点数。每个节点恰好被访问一次。
  • 空间复杂度:O(h),其中h为二叉树的高度。这是因为递归过程中最多会有h层的递归调用,每层递归调用会占用一定的栈空间。

注意事项

  • 需要正确地维护前一个节点 pre,它是中序遍历中最后一个访问的节点。
  • 比较节点值时,需要确保 pre 不为空,因为根节点没有前一个节点。

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

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

给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 小的元素(从 1 开始计数)。

示例 1:
在这里插入图片描述

输入:root = [3,1,4,null,2], k = 1
输出:1

示例 2:

在这里插入图片描述

输入:root = [5,3,6,2,4,null,null,1], k = 3
输出:3

提示:

  • 树中的节点数为 n
  • 1 <= k <= n <= 104
  • 0 <= Node.val <= 104

**进阶:**如果二叉搜索树经常被修改(插入/删除操作)并且你需要频繁地查找第 k 小的值,你将如何优化算法?

方法一:递归

这个问题可以通过中序遍历二叉树来解决。在中序遍历过程中,我们可以访问到节点的值,且值的顺序是递增的。因此,我们可以通过计数来找到第k个访问的节点。

  1. 初始化计数器:设置一个全局变量 tk 来记录还需要访问的节点数。
  2. 中序遍历:递归地遍历二叉树,先遍历左子树,然后访问当前节点,最后遍历右子树。
  3. 计数访问:每访问一个节点,tk 减1。
  4. 记录第k小的元素:当 tk 减至0时,记录当前节点的值到 ans,并停止遍历。
  5. 返回结果:遍历结束后,返回 ans
class Solution {int tk; // 记录还需要访问的节点数int ans; // 存储第k小的元素public int kthSmallest(TreeNode root, int k) {tk = k; // 初始化计数器tranverse(root); // 调用中序遍历方法return ans; // 返回第k小的元素}public void tranverse(TreeNode root) {if (root == null) {return;}tranverse(root.left); // 遍历左子树tk--; // 访问当前节点,计数器减1if (tk == 0) { // 如果这是第k次访问ans = root.val; // 更新答案为当前节点的值return; // 找到后立即停止遍历}tranverse(root.right); // 遍历右子树}
}

复杂度分析

  • 时间复杂度:O(k + n),其中n为二叉树的节点数。在最坏情况下,可能需要访问所有节点才能找到第k小的元素。
  • 空间复杂度:O(h),其中h为二叉树的高度。这是因为递归过程中最多会有h层的递归调用,每层递归调用会占用一定的栈空间。

199 二叉树的右视图

199. 二叉树的右视图

给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

示例 1:

在这里插入图片描述

输入: [1,2,3,null,5,null,4]
输出: [1,3,4]

示例 2:

输入: [1,null,3]
输出: [1,3]

示例 3:

输入: []
输出: []

提示:

  • 二叉树的节点个数的范围是 [0,100]
  • -100 <= Node.val <= 100

方法一:层序遍历

  1. 初始化:定义一个空列表 res 用来存储结果,如果根节点为空,直接返回空列表。
  2. 创建队列:使用 LinkedList 作为队列存储每层的节点。
  3. 根节点入队:将根节点添加到队列中。
  4. 遍历队列:当队列不为空时,执行以下步骤:
    • 记录当前层的节点数量 currentSize
    • 遍历当前层的每个节点,对于每个节点:
      • 如果有左子节点,将其添加到队列中。
      • 如果有右子节点,将其添加到队列中。
      • 如果当前节点是该层的最后一个节点(即遍历的节点索引 i 等于 currentSize - 1),将其值添加到结果列表 res 中。
  5. 返回结果:遍历结束后,返回结果列表 res
/*** 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<Integer> rightSideView(TreeNode root) {//题目直接用层序遍历即可求解//层序遍历思路如下://1.根节点入队//2.0 开始while循环,直到队列为空停止遍历//2.记录该层节点数量n,(记录当前队列中的节点个数)//3.遍历n次,每次遍历时将当前节点的左孩子和右孩子offer进队,同时poll掉当前节点//从以上可以看出,本题可以在遍历每层的时候,将每层的最后一个元素记录到结果中,因为每层的最后一个元素一定是最右边的节点//1.定义返回结果listList<Integer> res = new ArrayList<>();//根节点为空,返回空listif(root == null){return res;}//1.1 定义用来存储每层节点元素的队列,Deque是双端队列,我们在这里是需要实现先进先出即可。因此用QueueQueue<TreeNode> queue = new LinkedList<>();//2.根节点入队queue.offer(root);//2.0开始循环遍历,直到队列为空,说明遍历完毕跳出循环while(!queue.isEmpty()){//2.1 记录当前层的节点元素数量为currentSizeint currentSize = queue.size();for(int i = 0;i<currentSize;i++){TreeNode curr = queue.poll(); //获取队首的树节点,使用poll来移除并返回队首的元素if(curr.left != null) queue.offer(curr.left); // 左子树不为空,入队if(curr.right != null) queue.offer(curr.right); // 右子树不为空,入队//如果是当前层最后一个元素,应当在结果中记录下来。if(i == currentSize - 1){res.add(curr.val); }}}return res;}
}

复杂度分析

  • 时间复杂度:O(n),其中 n 是二叉树的节点数。每个节点都被访问了一次。
  • 空间复杂度:O(m),其中 m 是二叉树的宽度。在最坏的情况下,所有节点都在同一层,这时队列中存储的节点数最多,即二叉树的最大宽度。

114 二叉树展开为链表

114. 二叉树展开为链表

给你二叉树的根结点 root ,请你将它展开为一个单链表:

  • 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null
  • 展开后的单链表应该与二叉树 先序遍历 顺序相同。

示例 1:

在这里插入图片描述

输入:root = [1,2,5,3,4,null,6]
输出:[1,null,2,null,3,null,4,null,5,null,6]

示例 2:

输入:root = []
输出:[]

示例 3:

输入:root = [0]
输出:[0]

提示:

  • 树中结点数在范围 [0, 2000]
  • -100 <= Node.val <= 100

**进阶:**你可以使用原地算法(O(1) 额外空间)展开这棵树吗?

方法一:递归

本题和98题有点像。

通过递归实现。我们需要按照右左中的顺序遍历二叉树的节点,因为这样可以确保在处理当前节点之前,其左右子树已经被处理好并连接成了链表。

​ 和先序遍历顺序相同,先序遍历为中左右。
​ 考虑使用变形后序遍历的方式,即右左中来遍历,每次遍历都要记录当前节点遍历到的前一个节点为pre
​ 之后更新当前节点的右子树为pre,左子树为null
​ 为什么这样不用担心左子树丢失呢?因为按照右左中遍历顺序,一定是先将curr节点的右和左节点处理过后才处理curr
​ 即curr的左子树已经连在链表中了因此可以直接左子树置为null

可以参考如下过程理解

我们知道题目给定的遍历顺序其实就是先序遍历的顺序,所以我们能不能利用先序遍历的代码,每遍历一个节点,就将上一个节点的右指针更新为当前节点。先序遍历的顺序是 1 2 3 4 5 6。遍历到 2,把 1 的右指针指向 2。1 -> 2 3 4 5 6。遍历到 3,把 2 的右指针指向 3。1 -> 2 -> 3 4 5 6。... ...一直进行下去似乎就解决了这个问题。但现实是残酷的,原因就是我们把 1 的右指针指向 2,那么 1 的原本的右孩子就丢失了,也就是 5 就找不到了。解决方法的话,我们可以逆过来进行。我们依次遍历 6 5 4 3 2 1,然后每遍历一个节点就将当前节点的右指针更新为上一个节点。遍历到 5,把 5 的右指针指向 6。6 <- 5 4 3 2 1。遍历到 4,把 4 的右指针指向 5。6 <- 5 <- 4 3 2 1。

最终流程如下

  1. 递归终止条件:如果当前节点为 null,则返回。
  2. 递归处理右子树:首先递归处理当前节点的右子树。
  3. 递归处理左子树:然后递归处理当前节点的左子树。
  4. 扁平化当前节点:将当前节点的左子树置为 null,右子树指向前一个节点 pre
  5. 更新前一个节点:将当前节点设置为前一个节点 pre
  6. 递归返回:递归返回,直到所有节点都被处理。
class Solution {TreeNode pre = null;public void flatten(TreeNode root) {//1.确认递归终止条件if(root == null){return ;}//if(root.right != null) flatten(root.right);if(root.left != null) flatten(root.left);//处理中间节点,将当前节点左子树执行null,右子树指向前一个节点root.right = pre;root.left = null;pre = root;return ;}
}

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

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

给定两个整数数组 preorderinorder ,其中 preorder 是二叉树的先序遍历inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

示例 1:
在这里插入图片描述

输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]

示例 2:

输入: preorder = [-1], inorder = [-1]
输出: [-1]

提示:

  • 1 <= preorder.length <= 3000
  • inorder.length == preorder.length
  • -3000 <= preorder[i], inorder[i] <= 3000
  • preorderinorder无重复 元素
  • inorder 均出现在 preorder
  • preorder 保证 为二叉树的前序遍历序列
  • inorder 保证 为二叉树的中序遍历序列

方法一:递归

问题分析

重建二叉树的问题可以通过递归的方法解决。关键在于理解前序遍历和中序遍历的特点:

  1. 前序遍历:首先访问根节点,然后递归访问左子树,最后递归访问右子树。
  2. 中序遍历:首先递归访问左子树,然后访问根节点,最后递归访问右子树。

根据以上特点,我们可以得出以下结论:

  • 前序遍历的第一个节点总是当前子树的根节点。
  • 在中序遍历中找到根节点,可以确定左子树和右子树的边界。
算法逻辑
  1. 构建映射:首先,我们需要构建一个映射(HashMap),将中序遍历中的每个值映射到其在数组中的索引。这样我们可以快速找到根节点在中序遍历中的位置。
  2. 递归终止条件:如果前序遍历的起始索引大于或等于结束索引,或者中序遍历的起始索引大于或等于结束索引,说明当前子树为空,返回 null
  3. 确定根节点:前序遍历的第一个节点是整棵树的根节点。
  4. 找到根节点在中序遍历中的位置:使用映射找到根节点在中序遍历中的索引。
  5. 计算左右子树的边界
    • 左子树的中序遍历序列从起始索引到根节点索引(不含)。
    • 右子树的中序遍历序列从根节点索引的下一个位置到结束索引。
  6. 计算左子树的前序遍历序列
    • 左子树的前序遍历序列从根节点的下一个位置开始,长度等于左子树的中序遍历序列的长度。
  7. 递归构建左子树:使用左子树的前序和中序遍历序列递归构建左子树。
  8. 递归构建右子树:使用右子树的前序和中序遍历序列递归构建右子树。
  9. 组合子树:将递归构建的左右子树赋值给根节点的左右子节点。
/*** 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 {//preorder = [3,9,20,15,7]//inorder = [9,3,15,20,7]//1.根据preorder找到根节点3//2.根据根节点找到其在中序序列中的index = 1//3.切割中序序列为左中序[,index),右中序[index+1,) // 左中序[0,1),右中序[2,5) //4.切割前序序列为左前序[preorderStart+1,preorderStart+1+左节点数量),右前序[preorderStart+1+左节点数量,)// 左前序[1,2),右前序[2,5)//5.递归构造左子树,右子树Map<Integer,Integer> map = new HashMap<>();//保存中序序列的值的对应位置public TreeNode buildTree(int[] preorder, int[] inorder) {for(int i = 0;i<inorder.length;i++){map.put(inorder[i],i);}return buildTree(preorder,0,preorder.length,inorder,0,inorder.length);}private TreeNode buildTree(int[] preorder,int preorderStart,int preorderEnd,int[] inorder,int inorderStart,int inorderEnd){if(preorderStart >= preorderEnd || inorderStart >= inorderEnd){return null;}//1.根据preorder找到根节点3int val = preorder[preorderStart];TreeNode root = new TreeNode(val);//2.根据根节点找到其在中序序列中的index = 1int index = map.get(val);//3.切割中序序列为左中序[,index),右中序[index+1,) // 左中序[0,1),右中序[2,5) int left_inorderStart = inorderStart;int left_inorderEnd = index;int right_inorderStart = index+1;int right_inorderEnd = inorderEnd;//4.切割前序序列为左前序[preorderStart+1,preorderStart+1+左节点数量),右前序[preorderStart+1+左节点数量,)// 左前序[1,2),右前序[2,5)int left_preorderStart = preorderStart+1;int left_preorderEnd = preorderStart + 1 + left_inorderEnd - left_inorderStart;int right_preorderStart = left_preorderEnd;int right_preorderEnd = preorderEnd;//5.递归构造左子树,右子树root.left = buildTree(preorder,left_preorderStart,left_preorderEnd,inorder,left_inorderStart,left_inorderEnd);root.right = buildTree(preorder,right_preorderStart,right_preorderEnd,inorder,right_inorderStart,right_inorderEnd);return root;}
}

437 路径总和III

437. 路径总和 III

给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum路径 的数目。

路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。

示例 1:

在这里插入图片描述

输入:root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8
输出:3
解释:和等于 8 的路径有 3 条,如图所示。

示例 2:

输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:3

提示:

  • 二叉树的节点个数的范围是 [0,1000]
  • -109 <= Node.val <= 109
  • -1000 <= targetSum <= 1000

方法一:递归+前缀和

问题分析

这个问题可以通过递归和前缀和的思想来解决。前缀和是指从根节点开始,到当前节点为止的路径上的节点值之和。我们可以使用一个哈希表来存储前缀和的出现次数,这样当我们到达一个节点时,可以通过查找 (sum - targetSum) 的出现次数来确定有多少条路径的和等于 targetSum

算法逻辑
  1. 初始化:创建一个哈希表 cnt,用于存储前缀和的出现次数。初始时,插入 0 的计数为 1,因为如果路径和为 0,则没有节点,这是有效的。
  2. 递归终止条件:如果当前节点为 null,则返回 0
  3. 计算前缀和:到达当前节点时,计算从根节点到当前节点的路径和 sum
  4. 查找匹配的路径:使用 cnt.getOrDefault(sum - targetSum, 0) 查找有多少条路径的和为 targetSum
  5. 更新哈希表:将当前的路径和 sum 更新到哈希表中。
  6. 递归遍历左右子树:对左右子树递归调用 tranverse 函数,并更新路径和。
  7. 回溯:在返回之前,将当前路径和 sum 的计数从哈希表中减去,以避免影响其他路径的计算。
  8. 返回结果:返回满足条件的路径数量。
/*** 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 {//存放前缀和出现的次数Map<Long,Integer> cnt = new HashMap<>();public int pathSum(TreeNode root, int targetSum) {//使用前缀和的思想来解决这道问题。//二叉树中的前缀和定义为从根节点开始的路径元素和//用哈希表cnt统计前缀和的出现次数,那么当递归到节点node的时候,假设从根到node的路径元素和为s,//此时说明找到了cnt[s-targetSum]个符合要求的路径//初始化时,哈希表中应先放入(0,1)//遍历顺序可以用先序遍历从父节点到子节点cnt.put(0L,1);return tranverse(root,targetSum,0);}public int tranverse(TreeNode root,int targetSum,long sum){//好抽象的题目,也可能是因为将二叉树和回溯+前缀和结合到一起。//1.确定终止条件if(root == null){return 0;}//2.求出到当前节点的路径和(前缀和)sum += root.val;//3.这里,假如当前从根节点到当前节点和为10,targetSum为9,那么需要找的就是从根节点到某个节点路径和为1的路径数量int ans = cnt.getOrDefault(sum-targetSum,0);//3.1 更新哈希表cnt.merge(sum,1,Integer::sum);ans += tranverse(root.left,targetSum,sum);ans += tranverse(root.right,targetSum,sum);//3.2 回溯哈希表cnt.merge(sum,-1,Integer::sum);return ans;}
}

复杂度分析

  • 时间复杂度:O(n),其中 n 是二叉树的节点数。每个节点都被访问了一次。
  • 空间复杂度:O(n),递归栈的深度是树的高度,最多为O(n)。哈希表的大小在最坏情况下也是O(n)。

注意事项

  • 在递归过程中,确保在遍历完当前节点的左右子树后,回溯哈希表,以避免对后续计算造成影响。
  • 使用 Long 类型来存储路径和,以避免可能的溢出。

236 二叉树的最近公共祖先

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

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

示例 1:
在这里插入图片描述

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。

示例 2:
在这里插入图片描述

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出:5
解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。

示例 3:

输入:root = [1,2], p = 1, q = 2
输出:1

提示:

  • 树中节点数目在范围 [2, 105] 内。
  • -109 <= Node.val <= 109
  • 所有 Node.val 互不相同
  • p != q
  • pq 均存在于给定的二叉树中。

方法一:递归+回溯

问题分析

遇到这个题目首先想的是要是能自底向上查找就好了,这样就可以找到公共祖先了。那么二叉树如何可以自底向上查找呢?

通过回溯,二叉树回溯的过程就是从低到上。

后序遍历(左右中)就是天然的回溯过程,可以根据左右子树的返回值,来处理中节点的逻辑。

接下来就看如何判断一个节点是节点q和节点p的公共祖先呢。

首先最容易想到的一个情况:如果找到一个节点,发现左子树出现结点p,右子树出现节点q,或者 左子树出现结点q,右子树出现节点p,那么该节点就是节点p和q的最近公共祖先。

这个问题可以通过递归和回溯的方法解决。我们可以使用后序遍历来找到 pq 的最近公共祖先。

算法逻辑

  1. 递归终止条件:如果当前节点为空,返回 null
  2. 找到节点:如果当前节点是 pq,返回当前节点。
  3. 递归遍历:递归地在左子树上调用 lowestCommonAncestor 函数,寻找 pq
  4. 递归遍历:同样递归地在右子树上调用 lowestCommonAncestor 函数。
  5. 处理逻辑
    • 如果左子树和右子树的递归调用都返回了非 null 值,说明 pq 分别在当前节点的左右子树中,当前节点就是它们的最近公共祖先。
    • 如果左子树的递归调用返回了非 null 值而右子树返回了 null,说明 pq 在左子树中,返回左子树的递归调用结果。
    • 如果右子树的递归调用返回了非 null 值而左子树返回了 null,说明 pq 在右子树中,返回右子树的递归调用结果。
    • 如果左右子树的递归调用都返回了 null,说明 pq 都不在当前子树中,返回 null
/*** 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) {//这里我们需要将节点的情况往根节点一步步返回。因此在这里需要使用后序遍历。左右中,将左右节点的情况返回给中间的根节点//1.递归终止if(root == null){return null;}// 1.1 找到p或者q,将结果向上返回if(root == p || root == q){return root;}//2.开始递归TreeNode left = lowestCommonAncestor(root.left,p,q);TreeNode right = lowestCommonAncestor(root.right,p,q);//3.判断左右节点中是否包含p或q,即判断left或者right是否为空//3.1 如果left为空,right不为空,说明right出现了p或q,返回rightif(left == null && right != null){return right;}//3.2 如果right为空,left不为空,说明left出现了p或q,返回leftif(left != null && right == null){return left;}//3.3 如果right和left都不为空,返回rootif(left != null && right != null){return root;}//3.4 否则返回nullreturn null;}}

复杂度分析

  • 时间复杂度:O(n),其中 n 是二叉树的节点数。在最坏的情况下,每个节点都会被访问一次。
  • 空间复杂度:O(h),其中 h 是二叉树的高度。这是因为在递归过程中,最多会有树的高度那么多层的函数调用在递归栈上。

注意事项

  • 确保在递归过程中正确处理左右子树的返回值。
  • 在二叉搜索树中,这个问题可以更高效地解决,因为可以通过比较节点值和查找值来决定是遍历左子树还是右子树。

124 二叉树中的最大路径和

124. 二叉树中的最大路径和

二叉树中的 路径 被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。

路径和 是路径中各节点值的总和。

给你一个二叉树的根节点 root ,返回其 最大路径和

示例 1:

在这里插入图片描述

输入:root = [1,2,3]
输出:6
解释:最优路径是 2 -> 1 -> 3 ,路径和为 2 + 1 + 3 = 6

示例 2:
在这里插入图片描述

输入:root = [-10,9,20,null,null,15,7]
输出:42
解释:最优路径是 15 -> 20 -> 7 ,路径和为 15 + 20 + 7 = 42

提示:

  • 树中节点数目范围是 [1, 3 * 104]
  • -1000 <= Node.val <= 1000

方法一:递归

问题分析

理解路径和的概念。在这里我们引入一个最大贡献值的概念。最大贡献值是指以当前节点为根的子树中,能够贡献给根节点的最大路径和。

整体思路为

对于根节点而言,其最大路径和=左子树的最大贡献值(左子树贡献值>0)+右子树的最大贡献值(右子树贡献值>0)
那最大贡献值如何计算呢?如果当前为叶子节点,那么最大贡献值就是他本身。如果不是叶子节点,则节点的最大贡献值= 当前节点的值 + 左/右子树节点的最大贡献值!
在这里我们需要将左子树和右子树的结果一层层向上返回,所以是后序遍历。左右中

算法逻辑

  1. 理解路径和:路径和是一条从节点到节点的路径上所有值的和,不能有重复的节点。
  2. 最大路径和的特点:最大路径和不一定经过根节点,因此我们需要在每个节点处计算以该节点为根的子树的最大路径和。
  3. 后序遍历:使用后序遍历(左右中)的方式遍历二叉树。这是因为我们需要先计算左右子树的最大贡献值,然后再计算当前节点的最大路径和。
  4. 递归终止条件:如果当前节点为空,返回0。这表示当前路径不存在。
  5. 计算最大贡献值:对于当前节点的左右子树,递归计算它们的最大贡献值。由于路径不能有重复节点,如果子树的最大贡献值小于0,则在计算当前节点的最大路径和时忽略该子树。
  6. 更新全局最大路径和:如果以当前节点为根的路径和大于全局最大路径和,则更新全局最大路径和。
  7. 返回最大贡献值:返回当前节点的最大贡献值,以便父节点计算其最大路径和。
/*** 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 int maxSum = Integer.MIN_VALUE;public int maxPathSum(TreeNode root) {tranverse(root);return maxSum;}public int tranverse(TreeNode root){//记录每个节点的最大贡献值即可//对于根节点而言,其最大路径和=左子树的最大贡献值(左子树贡献值>0)+右子树的最大贡献值(右子树贡献值>0)//那最大贡献值如何计算呢?如果当前为叶子节点,那么最大贡献值就是他本身//在这里我们需要将左子树和右子树的结果一层层向上返回,所以是后序遍历。左右中if(root==null){return 0;}//后序遍历,左右中int LMax = Math.max(tranverse(root.left),0);int RMax = Math.max(tranverse(root.right),0);//中间节点//节点的最大路径和取决于该节点的值与该节点的左右子节点的最大贡献值int priceNewpath = root.val + LMax + RMax;maxSum = Math.max(priceNewpath,maxSum); //需要记录全局最大路径和。因为全局最大路径和不一定经过root节点//节点的最大贡献值= 当前节点的值 + 左/右节点的最大贡献值!return root.val + Math.max(LMax,RMax);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/880928.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

小程序-使用npm包

目录 Vant Weapp 安装 Vant 组件库 使用 Vant 组件 定制全局主题样式 API Promise化 1. 基于回调函数的异步 API 的缺点 2. 什么是 API Promise 化 3. 实现 API Promise 化 4.调用 Promise 化之后的异步 API 小程序对 npm 的支持与限制 目前&#xff0c;小程序中已经…

Java 之深入理解 String、StringBuilder、StringBuffer

前言 由于发现 String、StringBuilder、StringBuffer 面试的时候会经常问到&#xff0c;这里就顺便总结一下&#xff1a;本文重点会以这三个字符串类的性能、线程安全、存储结构这三个方面进行分析 ✨上期回顾&#xff1a;Java 哈希表 ✨目录 前言 String 介绍 String 的不可变…

全局安装cnpm并设置其使用淘宝镜像的仓库地址(地址最新版)

npm、cnpm和pnpm基本概念 首先介绍一下npm和cnpm是什么&#xff0c;顺便说一下pnpm。 npm npm&#xff08;Node Package Manager&#xff09;是Node.js的默认包管理器&#xff0c;用于安装、管理和分享JavaScript代码包。它是全球最大的开源库生态系统之一&#xff0c;提供了数…

如何使用ssm实现基于HTML的中国传统面食介绍网站的搭建+vue

TOC ssm758基于HTML的中国传统面食介绍网站的搭建vue 第1章 绪论 1.1选题动因 当前的网络技术&#xff0c;软件技术等都具备成熟的理论基础&#xff0c;市场上也出现各种技术开发的软件&#xff0c;这些软件都被用于各个领域&#xff0c;包括生活和工作的领域。随着电脑和笔…

微服务SpringGateway解析部署使用全流程

官网地址&#xff1a; Spring Cloud Gateway 目录 1、SpringGateway简介 1、什么是网关 2、为什么用网关【为了转发】 2、应用&#xff1a; 1.启动nacos 2.创建网关项目 3.网关配置1 4.网关配置2【了解】 5.过滤器配置【了解】 1、SpringGateway简介 核心功能有三个&…

Webpack模式-Resolve-本地服务器

目录 ResolveMode配置搭本地服务器区分环境配置 Resolve 前面学习时使用了各种各样的模块依赖&#xff0c;这些模块可能来自于自己编写的代码&#xff0c;也可能来自第三方库&#xff0c;在 Webpack 中&#xff0c;resolve 是用于解析模块依赖的配置项&#xff0c;它决定了 We…

每日OJ题_牛客_DP13[NOIP2002 普及组]过河卒_路径dp_C++_Java

目录 牛客_DP13[NOIP2002 普及组]过河卒_路径dp 题目解析 C代码1 C代码2 Java代码 牛客_DP13[NOIP2002 普及组]过河卒_路径dp [NOIP2002 普及组] 过河卒_牛客题霸_牛客网 (nowcoder.com) 描述&#xff1a; 棋盘上 A点有一个过河卒&#xff0c;需要走到目标 B点。卒行走的…

业务封装与映射 -- 业务映射路径

为什么需要封装映射 OTN网络客户业务种类繁多&#xff08;例如SDH、以太网、视频&#xff09;&#xff0c;且业务大小不一&#xff08;例如STM-1、STM-4、STM-16&#xff09;&#xff0c;为了便于传输、管理客户业务&#xff0c;保证设备间互联互通&#xff0c;ITU-T定义了OTN接…

《深度学习》OpenCV 角点检测、特征提取SIFT 原理及案例解析

目录 一、角点检测 1、什么是角点检测 2、检测流程 1&#xff09;输入图像 2&#xff09;图像预处理 3&#xff09;特征提取 4&#xff09;角点检测 5&#xff09;角点定位和标记 6&#xff09;角点筛选或后处理&#xff08;可选&#xff09; 7&#xff09;输出结果 3、邻域…

物联网智能项目全面解析

目录 引言 一、物联网概述 1.1 什么是物联网 1.2 物联网的历史与发展 二、物联网智能项目分类 三、关键组件与技术 3.1 传感器和执行器 3.2 连接技术 3.3 数据处理与分析 3.4 用户界面 四、物联网智能项目案例分析 4.1 智能家居 4.2 智慧城市 4.3 工业物联网 4.4…

前端编程艺术(2)----CSS

目录 1.CSS 2.CSS引入 3.选择器 1.标签选择器 2.类选择器 3.id选择器 4.属性选择器 5.后代选择器 5.直接子元素选择器 6.伪类选择器 链接相关 动态伪类 结构化伪类 否定伪类 其他伪类 UI元素状态伪类 4.字体 1.font-family 2.font-size 3.font-style 4.fo…

C++之多线程

前言 多线程和多进程是并发编程的两个核心概念,它们在现代计算中都非常重要,尤其是在需要处理大量数据、提高程序性能和响应能力的场景中。 多线程的重要性: 资源利用率:多线程可以在单个进程中同时执行多个任务,这可以更有效地利用CPU资源,特别是在多核处理器上。 性…

奔驰EQS450suv升级增强AR抬头显示HUD案例分享

以下是奔驰 EQS450 SUV 升级增强版 AR 抬头显示的一般改装案例步骤及相关信息&#xff1a; 配件&#xff1a;通常包括显示屏、仪表模块、饰板等。 安装步骤&#xff1a; 1. 拆下中控的仪表。 2. 在仪表上预留位置切割出合适的孔位&#xff0c;用于安装显示器。 3. 将显示器…

(IDEA)spring项目导入本地jar包方法和项目打包时找不到引入本地jar包的问题解决方案

系列文章目录 文章目录 系列文章目录一、&#xff08;IDEA&#xff09;spring项目导入本地jar包方法和项目打包时找不到引入本地jar包的问题解决方案1.资料 一、&#xff08;IDEA&#xff09;spring项目导入本地jar包方法和项目打包时找不到引入本地jar包的问题解决方案 1.资料…

Python案例--水仙花数的探索之旅

一、引言 水仙花数&#xff0c;也称为阿姆斯特朗数&#xff0c;是一种特殊的三位数&#xff0c;其各位数字的立方和等于其本身。例如&#xff0c;153就是一个水仙花数&#xff0c;因为 135333153135333153。这种数字的发现不仅展示了数字的内在美&#xff0c;也激发了人们对数…

大学学校用电安全远程监测预警系统

1.概述&#xff1a; 该系统是基于移动互联网、云计算技术&#xff0c;通过物联网传感终端&#xff0c;将办公建筑、学校、医院、工厂、体育场馆、宾馆、福利院等人员密集场所的电气安全数据&#xff0c;实时传输至安全用申管理服务器&#xff0c;为用户提供不间断的数据跟踪&a…

五子棋双人对战项目(4)——匹配模块(解读代码)

目录 一、约定前后端交互接口的参数 1、websocket连接路径 2、构造请求、响应对象 二、用户在线状态管理 三、房间管理 1、房间类&#xff1a; 2、房间管理器&#xff1a; 四、匹配器(Matcher) 1、玩家实力划分 2、加入匹配队列&#xff08;add&#xff09; 3、移除…

解决ModuleNotFoundError: No module named ‘torchcrf‘

运行深度学习程序时候&#xff0c;出现报错&#xff1a;ModuleNotFoundError: No module named torchcrf 将 from torchcrf import CRF 改为 from TorchCRF import CRF

C#案例 | 基于C#语言在Excel中进行二次开发(一):简单系统搭建:打印输出“Hello Excel C#”

基于C#语言在Excel中进行二次开发&#xff08;一&#xff09;&#xff1a;简单系统搭建&#xff1a;打印输出”Hello Excel & C#” 实现效果第一步&#xff1a;前期准备第二步&#xff1a;打开VS 2022&#xff0c;创建项目第三步&#xff1a;程序界面设计 实现效果 在Exce…

《蓝桥杯算法入门》(C/C++、Java、Python三个版本)24年10月出版

推荐&#xff1a;《算法竞赛》&#xff0c;算法竞赛大全书&#xff0c;网购&#xff1a;京东 天猫  当当 文章目录 《蓝桥杯算法入门》内容简介本书读者对象作者简介联系与交流《蓝桥杯算法入门 C/C》版目录 《蓝桥杯算法入门 Java》版目录 《蓝桥杯算法入门 Python》版目录 …