代码随想录训练营D17-二叉树篇 p4 | 110.平衡二叉树、257. 二叉树的所有路径、404.左叶子之和
迭代法,大家可以直接过,二刷有精力的时候 再去掌握迭代法。
(一) 110.平衡二叉树 (优先掌握递归)
再一次涉及到,什么是高度,什么是深度,可以巩固一下。
深度:从根结点算起,力扣上规定根结点深度为1,也有规定根结点深度为0的。
高度:从叶子结点算起,叶子结点的高度为1,叶子结点下面的空结点高度为0.
求深度可以从上到下去查 所以需要前序遍历(中左右),而高度只能从下到上去查,所以只能后序遍历(左右中).
但 104.二叉树的最大深度 中求的是二叉树的最大深度,也用的是后序遍历。因为最大深度、最小深度,是与最大高度、最小高度相等的,所以可以使用后序遍历。
1. 思路
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
由于本题涉及的是树的高度,高度是从下向上看的,从叶子结点算起,最后才算到根结点,所以采用后序遍历。
2. 代码
树中一个结点的左右子树绝对值超过1了,这个结点领导的树不是平衡二叉树了,那么上面的数也不是平衡二叉树了,所以此时返回-1
递归三部曲:
1.确定参数类型和返回值类型。参数类型:树结点;返回值,int 返回当前节点树的高度。递归过程中,任一结点不为平衡二叉树时,返回-1.
2.终止条件。遇到空结点时,返回高度0.
3.单层递归逻辑。
后序遍历,左右中。
先递归调用左子树,求左子树高度
再递归调用右子树,求右子树的高度
前面所求的子树高度若为-1,那么这里也直接返回-1
最后,求根,若左右子树高度差的绝对值 > 1,那么已经不是平衡二叉树了,返回-1。否则,还是平衡二叉树,此时应返回当前结点的高度 = 1 + max(左、右子树)
public boolean isBalanced(TreeNode root) {
return getHeight(root) != -1;
}
//后序遍历
int getHeight(TreeNode node){
if(node == null){
return 0;
}
//左右根
int left = getHeight(node.left);
int right = getHeight(node.right);
if(left == -1 || right == -1){
return -1;
}
//根
int minus = Math.abs(left - right);
if(minus > 1){
//差值绝对值大于1,则不是平衡二叉树
return -1;
}else {
return 1 + Math.max(left, right);
}
}
(二) 257. 二叉树的所有路径 (优先掌握递归)
这是大家第一次接触到回溯的过程, 我在视频里重点讲解了 本题为什么要有回溯,已经回溯的过程。
如果对回溯 似懂非懂,没关系, 可以先有个印象。
1. 思路
本题使用前序遍历。根左右:1253
125,遍历到叶子结点5时开始回溯,53弹出,此时剩1,继续遍历13
得到两条到叶子结点的路径125,13
若使用后序遍历。左右根:5231,无法得到125,13这两条路径
若使用中序遍历。左根右:2513,无法得到125,13这两条路径
因为我们要把路径记录下来,需要回溯来回退一个路径再进入另一个路径。
1)124到达叶子结点,先将当前路径124保存,再回溯到2
2)1257到达叶子结点,保存1257,回溯到5
3)1258,保存,回溯到1
4)136,保存,回溯到1
递归三部曲-函数名traversal
1)递归函数的参数及返回值。
这里是要保存所有路径,而不是计算高度深度等,所以这里不需要返回值,因此返回值是void。
参数:二叉树结点、当前路径的list-paths,以及最终组合上了‘->’的结果集list-res
2)确定终止条件。
因为这里要求的是到叶子结点的路径,所以到叶子结点就应该停止。
if(node.left == null && node.right == null)
3)单层递归逻辑
先序遍历–根左右
先将当前结点存在paths中。
遇到叶子节点,则代表当前路径已经找完,可以存到完善其形式并加入res中了。
接下来,就是继续先序遍历的‘左右遍历’
若左孩子不为null,则traversal左孩子,同时在递归下面紧接着回溯。
同理右孩子。
回溯和递归是一一对应的,有一个递归,就要有一个回溯。
2. 代码
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;
}
//前序遍历(根左右) 递归 + 回溯
void traversal (TreeNode node, List<Integer> paths, List<String> res){
//这里为什么对于根结点的处理要放在终止条件之前。若放在终止条件之后,则叶子结点不会被加到paths中。
paths.add(node.val);
//终止条件.若是叶子节点,直接将paths中数据组装好存到res中。
if(node.left == null && node.right == null){
StringBuilder sb = new StringBuilder();
for(Integer num : paths){
sb.append(num);
sb.append("->");
}
sb.delete(sb.length() - 2, sb.length());
res.add(sb.toString());
}
//左右
//为什么这里不用先判断node是否为空?
//因为首先在binaryTreePaths判断了首先进入的root一定不为空;
//在traversal的前面遇到叶子结点就返回了,没有机会接触空结点。
if(node.left != null){
traversal(node.left, paths, res);//递归
paths.remove(paths.size() - 1);//回溯
}
if(node.right != null){
traversal(node.right, paths, res);//递归
paths.remove(paths.size() - 1);//回溯
}
}
(三) 404.左叶子之和 (优先掌握递归)
其实本题有点文字游戏,搞清楚什么是左叶子,剩下的就是二叉树的基本操作。
1. 思路
首先明确左叶子的定义:首先是叶子结点,并且是其父亲结点的左孩子。
本题的目的:计算这所有左叶子数值的和。
这种计算总和的二叉树的题目,都可以考虑是后序遍历。后序遍历,先左右,后根,刚好把左右子树的统计的和汇总到根结点处。
之前的一些二叉树题目都是在遍历到6时,判断一下这是不是我们需要的节点。而本题要在遍历到9时进行判断(条件是,当前节点9不为空,并且它的左孩子是叶子结点),这时这道题与其他题目的区别之处。
递归三部曲。
1)返回值int,参数二叉树结点
2)终止条件。为空结点时返回0,为叶子节点时,返回0
3)单层递归逻辑。
后序遍历。
若有左孩子。递归这个左孩子,存左侧 左叶子结点和 leftSum
若有右孩子,递归这个右孩子, 存右侧 左叶子结点和 rightSum
处理根节点。
若根节点,有左孩子且左孩子是叶子结点,存当前结点左叶子结点值
三数相加,返回
2. 代码
public int sumOfLeftLeaves(TreeNode root) {
//用后序遍历
//终止条件 空结点时、叶子结点时。
// 因为现在判断是否要这个数的时机在左叶子的父结点那里
if(root == null || (root.left == null && root.right == null)){
return 0;
}
//左右根
int leftSum = sumOfLeftLeaves(root.left);
int rightSum = sumOfLeftLeaves(root.right);
int midSum = 0;
if(root.left != null && root.left.left == null && root.left.right == null){
midSum = root.left.val;
}
return leftSum + rightSum + midSum;
}