110.平衡二叉树
力扣题目链接(opens new window)
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
示例 1:
给定二叉树 [3,9,20,null,null,15,7]
返回 true 。
class Solution {/*** 递归法*/public boolean isBalanced(TreeNode root) {return getHeight(root) != -1;}private int getHeight(TreeNode root) {if (root == null) {return 0;}int leftHeight = getHeight(root.left);if (leftHeight == -1) {return -1;}int rightHeight = getHeight(root.right);if (rightHeight == -1) {return -1;}// 左右子树高度差大于1,return -1表示已经不是平衡树了if (Math.abs(leftHeight - rightHeight) > 1) {return -1;}return Math.max(leftHeight, rightHeight) + 1;}
}
我遇到的问题
1.本题中根据后序的逻辑,是从叶子节点递归调用还是从根节点呢 ?
在递归遍历二叉树时,最先达到的是叶子节点(因为每次递归调用都会先处理子节点),但这些叶子节点的信息会逐级向上传递,直到根节点。
具体来说,在这个平衡二叉树的检查算法中:
-
递归调用顺序:首先,递归会深入到最左边的叶子节点,然后逐级向上返回,接着再深入到右子树的最左边叶子节点,如此往复,直到遍历完整棵树。这是后序遍历的特性,即先处理左子树,再处理右子树,最后处理根节点(但在这个场景中,我们实际上并没有在根节点做额外的处理,而是在返回的过程中进行了平衡性的检查)。
-
检查过程:虽然递归调用是从根节点开始的,但实际的检查(即比较左右子树的高度)是在递归调用的返回过程中进行的。当递归调用到达叶子节点时(即
root == null
的情况),它会返回高度0
。然后,这个信息会逐级向上传递,每一层都会基于其子节点的高度来计算自己的高度,并检查是否平衡。 -
信息传递:叶子节点的高度(
0
)会作为递归调用的返回值逐级向上传递。在传递过程中,每一层都会检查当前节点是否平衡,并据此返回-1
(不平衡)或当前子树的高度(平衡)。 -
结果确定:最终,当递归调用返回到根节点时,
isBalanced
方法会根据getHeight(root)
的返回值来确定整个树是否平衡。
所以,检查是在向叶子节点遍历的过程中进行的,但信息传递和结果确定是从叶子节点逐级向上到根节点的”。
现在我们讲个故事来理解这个过程
想象您正在一棵二叉树中行走,从根节点开始。您的任务是检查这棵树是否平衡(即任何节点的两个子树的高度差不超过1)。
- 向叶子节点遍历:
- 您首先走向根节点的左子树。
- 在左子树中,您继续向左走,直到到达一个叶子节点(没有子节点的节点)。
- 到达叶子节点后,您知道这个节点的高度是0。
- 然后,您返回上一级,并向右走,继续这个过程,直到您检查了左子树中的所有节点。
- 接着,您回到根节点,并重复这个过程,但这次是检查右子树。
在这个过程中,您实际上是在向叶子节点遍历,并在这个过程中进行平衡性的检查(尽管实际的检查是在返回过程中基于子树的高度进行的)。
- 信息传递和结果确定:
- 当您到达一个叶子节点并确定其高度为0时,这个信息会“传递”给它的父节点。
- 父节点会基于其左右子树的高度(这些高度是通过递归调用得到的)来计算自己的高度,并检查是否平衡。
- 如果父节点平衡,它会将自己的高度信息“传递”给它的父节点,依此类推,直到根节点。
- 最终,当所有递归调用都返回时,根节点会知道整棵树是否平衡。
这个过程就像是您在树中行走时,不断地收集信息(子树的高度),并在返回时基于这些信息做出决策(树是否平衡)。
对于叶子节点来说:
- 当
root
为某个叶子节点时,它的left
和right
指针中至少有一个(通常是两个)是null
。 - 当递归调用到达这个
null
指针时,会返回0
。 - 这个
0
会作为递归调用的结果返回给上一层调用,即成为上一层调用中leftHeight
或rightHeight
的值。
257. 二叉树的所有路径
力扣题目链接(opens new window)
给定一个二叉树,返回所有从根节点到叶子节点的路径。
说明: 叶子节点是指没有子节点的节点。
示例:
思路
这道题目要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。
在这道题目中将第一次涉及到回溯,因为我们要把路径记录下来,需要回溯来回退一个路径再进入另一个路径。
前序遍历以及回溯的过程如图:
我们先使用递归的方式,来做前序遍历。要知道递归和回溯就是一家的,本题也需要回溯。
class Solution {/*** 递归法*/public List<String> binaryTreePaths(TreeNode root) {List<String> res = new ArrayList<>();// 存最终的结果if (root == null) {return res;}List<Integer> paths = new ArrayList<>();// 作为结果中的路径traversal(root, paths, res);return res;}private void traversal(TreeNode root, List<Integer> paths, List<String> res) {paths.add(root.val);// 前序遍历,中// 遇到叶子结点if (root.left == null && root.right == null) {// 输出StringBuilder sb = new StringBuilder();// StringBuilder用来拼接字符串,速度更快for (int i = 0; i < paths.size() - 1; i++) {sb.append(paths.get(i)).append("->");}sb.append(paths.get(paths.size() - 1));// 记录最后一个节点res.add(sb.toString());// 收集一个路径return;}// 递归和回溯是同时进行,所以要放在同一个花括号里if (root.left != null) { // 左traversal(root.left, paths, res);paths.remove(paths.size() - 1);// 回溯}if (root.right != null) { // 右traversal(root.right, paths, res);paths.remove(paths.size() - 1);// 回溯}}
}
404.左叶子之和
力扣题目链接(opens new window)
计算给定二叉树的所有左叶子之和。
示例:
思路
首先要注意是判断左叶子,不是二叉树左侧节点,所以不要上来想着层序遍历。
因为题目中其实没有说清楚左叶子究竟是什么节点,那么我来给出左叶子的明确定义:节点A的左孩子不为空,且左孩子的左右孩子都为空(说明是叶子节点),那么A节点的左孩子为左叶子节点
大家思考一下如下图中二叉树,左叶子之和究竟是多少?
其实是0,因为这棵树根本没有左叶子!
但看这个图的左叶子之和是多少?
相信通过这两个图,大家对最左叶子的定义有明确理解了。
递归法
递归的遍历顺序为后序遍历(左右中),是因为要通过递归函数的返回值来累加求取左叶子数值之和。
递归三部曲:
- 确定递归函数的参数和返回值
判断一个树的左叶子节点之和,那么一定要传入树的根节点,递归函数的返回值为数值之和,所以为int
使用题目中给出的函数就可以了。
- 确定终止条件
如果遍历到空节点,那么左叶子值一定是0
if (root == NULL) return 0;
注意,只有当前遍历的节点是父节点,才能判断其子节点是不是左叶子。 所以如果当前遍历的节点是叶子节点,那其左叶子也必定是0,那么终止条件为:
if (root == NULL) return 0;
if (root->left == NULL && root->right== NULL) return 0; //其实这个也可以不写,如果不写不影响结果,但就会让递归多进行了一层。
- 确定单层递归的逻辑
当遇到左叶子节点的时候,记录数值,然后通过递归求取左子树左叶子之和,和 右子树左叶子之和,相加便是整个树的左叶子之和。
class Solution {public int sumOfLeftLeaves(TreeNode root) {if (root == null) return 0;int leftValue = sumOfLeftLeaves(root.left); // 左int rightValue = sumOfLeftLeaves(root.right); // 右int midValue = 0;if (root.left != null && root.left.left == null && root.left.right == null) { midValue = root.left.val;}int sum = midValue + leftValue + rightValue; // 中return sum;}
}
我遇到的问题
1. sum = midValue + leftValue + rightValue; sum为什么要这么求
在sumOfLeftLeaves
方法中,int sum = midValue + leftValue + rightValue;
这行代码是用来计算并存储当前节点及其左右子树中所有左叶子节点的值之和的。
让我们逐一看看这些变量:
midValue
:- 这个变量用来存储当前节点的左子节点(如果它是一个左叶子节点)的值。
- 如果当前节点的左子节点存在且没有自己的子节点(即它是一个叶子节点),则
midValue
会被设置为这个左叶子节点的值。 - 如果当前节点的左子节点不存在或不是叶子节点,则
midValue
保持为0。
leftValue
:- 这个变量是通过递归调用
sumOfLeftLeaves
方法计算得到的,它表示当前节点的左子树中所有左叶子节点的值之和。 - 如果当前节点没有左子节点,则
leftValue
为0(因为递归的基准情况会返回0)。
- 这个变量是通过递归调用
rightValue
:- 类似于
leftValue
,这个变量是通过递归调用sumOfLeftLeaves
方法计算得到的,它表示当前节点的右子树中所有左叶子节点的值之和。 - 注意这里有点微妙:即使我们是在考虑左叶子节点,我们仍然需要递归地检查右子树,因为右子树中可能包含作为其父节点左子节点的叶子节点(即这些叶子节点在它们自己的父节点看来是左叶子节点)。然而,由于我们只对“左叶子节点”感兴趣,并且这个定义是相对于它们的直接父节点而言的,所以右子树中的这些左叶子节点仍然会被正确地计算在内。
- 类似于
现在,int sum = midValue + leftValue + rightValue;
这行代码将这三个值相加,得到当前节点及其左右子树中所有左叶子节点的值之和。这个总和随后被返回给上一层递归调用,直到最终到达根节点,此时得到的总和就是整个二叉树中所有左叶子节点的值之和。
简而言之,这行代码是递归过程中计算总和的关键步骤,它结合了当前节点的左叶子节点值(如果有的话)、左子树中所有左叶子节点的值之和以及右子树中所有左叶子节点的值之和。
222.完全二叉树的节点个数
力扣题目链接(opens new window)
给出一个完全二叉树,求出该树的节点个数。
示例 1:
- 输入:root = [1,2,3,4,5,6]
- 输出:6
示例 2:
- 输入:root = []
- 输出:0
示例 3:
- 输入:root = [1]
- 输出:1
提示:
- 树中节点的数目范围是[0, 5 * 10^4]
- 0 <= Node.val <= 5 * 10^4
- 题目数据保证输入的树是 完全二叉树
普通二叉树
首先按照普通二叉树的逻辑来求。
这道题目的递归法和求二叉树的深度写法类似, 递归遍历的顺序依然是后序(左右中)。
#递归
- 确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回以该节点为根节点二叉树的节点数量,所以返回值为int类型。
先用c分步骤写出
代码如下:
int getNodesNum(TreeNode* cur) {
- 确定终止条件:如果为空节点的话,就返回0,表示节点数为0。
代码如下:
if (cur == NULL) return 0;
- 确定单层递归的逻辑:先求它的左子树的节点数量,再求右子树的节点数量,最后取总和再加一 (加1是因为算上当前中间节点)就是目前节点为根节点的节点数量。
代码如下:
int leftNum = getNodesNum(cur->left); // 左
int rightNum = getNodesNum(cur->right); // 右
int treeNum = leftNum + rightNum + 1; // 中
return treeNum;
完全二叉树
在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^(h-1) 个节点。
来举一个典型的例子如题:
完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。
对于情况一,可以直接用 2^树深度 - 1 来计算,注意这里根节点深度为1。
对于情况二,分别递归左孩子,和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后依然可以按照情况1来计算。
完全二叉树(一)如图:
完全二叉树(二)如图:
可以看出如果整个树不是满二叉树,就递归其左右孩子,直到遇到满二叉树为止,用公式计算这个子树(满二叉树)的节点数量。
这里关键在于如何去判断一个左子树或者右子树是不是满二叉树呢?
在完全二叉树中,如果递归向左遍历的深度等于递归向右遍历的深度,那说明就是满二叉树。如图:
在完全二叉树中,如果递归向左遍历的深度不等于递归向右遍历的深度,则说明不是满二叉树,如图:
class Solution {/*** 针对完全二叉树的解法** 满二叉树的结点数为:2^depth - 1*/public int countNodes(TreeNode root) {if (root == null) return 0;TreeNode left = root.left;TreeNode right = root.right;int leftDepth = 0, rightDepth = 0; // 这里初始为0是有目的的,为了下面求指数方便while (left != null) { // 求左子树深度left = left.left;leftDepth++;}while (right != null) { // 求右子树深度right = right.right;rightDepth++;}if (leftDepth == rightDepth) {return (2 << leftDepth) - 1; // 注意(2<<1) 相当于2^2,所以leftDepth初始为0}return countNodes(root.left) + countNodes(root.right) + 1;}
}