代码随想录训练营D15-二叉树篇 p2 | 层序遍历 10 、226.翻转二叉树、101.对称二叉树 2
- (一)102. 二叉树的层序遍历
- (二)层序遍历 9 道题目
- (三) 226.翻转二叉树 (优先掌握递归)
- (四)101. 对称二叉树 (优先掌握递归)
(一)102. 二叉树的层序遍历
1.思路
栈是先进后出,所以迭代法使用栈来代替底层的递归;树的递归相当于图的深度优先搜索遍历。
而层次遍历,一层一层由内而外,与队列的先进先出是相符合的。树的层次遍历相当于图的广度优先搜索。
题目的关键是,如何确定哪些结点是一层的。
通过一个变量 cnt来记录每层的结点数。
首先,根结点6入队。
开始外层循环,条件是队列不为空。
更新cnt = queue.size,现在是1
开始内层while循环,负责把当前层结点都出队(出队同时数值记录到数组),并且其孩子们都入队,循环条件是cnt>0(只有cnt>0才是当前层)
直到循环结束,返回数组
2.代码
//每层结点的数值放在一个list中,再将整体结果放在一个list中
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if(root == null){
return result;
}
//创建队列,根结点入队
Deque<TreeNode> queue = new ArrayDeque<>();
queue.add(root);
while(!queue.isEmpty()){
int cnt = queue.size();
//创建当前层的数组
List<Integer> curLevel = new ArrayList<>();
while(cnt-- > 0){//将当前层都出队(出队同时加入数组),并且将孩子们入队
TreeNode node = queue.poll();
curLevel.add(node.val);
if(node.left != null){
queue.add(node.left);
}
if(node.right != null){
queue.add(node.right);
}
}
//把每层结点的list放入result list
result.add(curLevel);
}
return result;
}
(二)层序遍历 9 道题目
[No.i] 107. 二叉树的层序遍历 II
1. 思路
1.俺の思路
仍旧按照上一题正常的层序遍历的思路,将每一层的元素存到一个数组中,再将这些数组存到一个大数组中。然后再将大数组中的数组们reverse一下。
(给牛完了,开始在汉语里面夹英语了)
2.代码随想录
可以将result表创建为LinkedList类型的,这样在插入时可以使用头插法
LinkedList<List<Integer>> result = new LinkedList<>();//左侧类型一定是LinkedList
result.addFirst(node);//使用头插法插入结点
2. 代码
public List<List<Integer>> levelOrderBottom(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if(root == null){
return result;
}
//创建队列
Deque<TreeNode> queue = new ArrayDeque();
queue.add(root);
while(!queue.isEmpty()){
//记录当前层要出队结点的数量cnt;每层都要创建数组,记录当前层的结点
int cnt = queue.size();
List<Integer> curList = new ArrayList<>();
while(cnt-- > 0){
//当前层结点出队,数值存入当前层数组;将左右孩子入队
TreeNode node = queue.poll();
curList.add(node.val);
if(node.left != null){
queue.add(node.left);
}
if(node.right != null){
queue.add(node.right);
}
}
//一层的遍历结束,将当前层数组放入总数组中
result.add(curList);
}
Collections.reverse(result);
return result;
}
[No.ii] 199. 二叉树的右视图
1. 思路
1.俺の思路
按前面层序遍历的思路,每层循环开始前使用变量cnt记录了队列中的结点个数(即当前层的结点数),因此本题中可以使当前层一直出队,直到当前层只剩一个结点,将该结点放入result list中。
2. 代码
public List<Integer> rightSideView(TreeNode root) {
List<Integer> result = new ArrayList<>();
//先判空
if(root == null){
return result;
}
Queue<TreeNode> queue = new ArrayDeque<>();
queue.add(root);
while(!queue.isEmpty()){
int cnt = queue.size();
//开始每层的循环
while(cnt-- > 0){
//属于当前层的结点出队,若有左右孩子也依次入队
TreeNode node = queue.poll();
if(node.left != null){
queue.add(node.left);
}
if(node.right != null){
queue.add(node.right);
}
//直到当前层结点数为1时,才存入result数组,由于我实在while循环中提前--了cnt
//所以是cnt为0时,就将结点存入result中
if(cnt == 0){
result.add(node.val);
}
}
}
return result;
}
[No.iii] 637. 二叉树的层平均值
1. 思路
1.俺の思路
由题意,即计算每一层的平均值,再将平均值放入result数组中。
层序遍历并能区分每层的思路:
1.借用队列。因为队列是先进先出,符合层序遍历的顺序
2.使用一个变量cnt记录每层结点的个数,以此来控制可以刚好将同一层的结点从队列中输出。
算法:
创建result数组,并判空输入的根结点root,若root为null,直接返回result。创建辅助队列queue,将根结点入队
进入外层while循环,进行条件是队列不为空;
进入内层while,每一层(层是指 层次遍历的层)的循环,进行条件是cnt>0,以此来保证在while中操作的都是当前层的结点。在内层while中,队头结点出队,若有左右孩子,则左右孩子依次入队,将当前结点的值累加到sum。
在内层while结束后,计算平均值。
2. 代码
public List<Double> averageOfLevels(TreeNode root) {
List<Double> result = new ArrayList<>();
if(root == null){
return result;
}
Deque<TreeNode> queue = new ArrayDeque<>();
queue.add(root);
while(!queue.isEmpty()){
int cnt = queue.size();
int num = cnt;
double sum = 0;
//开始当前层的一系列操作
while(cnt-- > 0){
//队头结点出队,若有左右结点,则左右结点入队
TreeNode node = queue.poll();
if(node.left != null){
queue.add(node.left);
}
if(node.right != null){
queue.add(node.right);
}
sum += node.val;
}
result.add(sum/num);
}
return result;
}
[No.iv] 429. N 叉树的层序遍历
1. 思路
1.俺の思路
仍旧是遍历树,将结点放入队列中。
层序遍历并能区分每层的思路:
1.借用队列。因为队列是先进先出,符合层序遍历的顺序
2.使用一个变量cnt记录每层结点的个数,以此来控制可以刚好将同一层的结点从队列中输出。
算法:
1.创建result数组,并判空输入的根结点root,若root为null,直接返回result。
2.创建辅助队列queue,将根结点入队。
3.进入外层while循环,进行条件是队列不为空;
4.进入内层while,每一层(层是指 层次遍历的层)的循环,进行条件是cnt>0,以此来保证在while中操作的都是当前层的结点。
在内层while中,首先创建一个list,用于存当前层的结点。队头结点出队,若孩子不为null,则一直遍历孩子结点的list,并将孩子结点入队。
内层while结束后,将当前层的数组add进result数组中。
2. 代码
public List<List<Integer>> levelOrder(Node root) {
List<List<Integer>> result = new ArrayList<>();
if(root == null){
return result;
}
Deque<Node> queue = new ArrayDeque<>();
queue.add(root);
while(!queue.isEmpty()){
//创建当前层的数组,并先记好当前层的结点数
int cnt = queue.size();
List<Integer> curList = new ArrayList<>();
while(cnt-- > 0){
Node node = queue.poll();
if(!node.children.isEmpty()){
List<Node> children = node.children;
for(Node child : children){
queue.add(child);
}
}
curList.add(node.val);
}
result.add(curList);
}
return result;
}
[No.v] 515. 在每个树行中找最大值
1. 思路
1.俺の思路
层序遍历并能区分每层的思路:
1.借用队列。因为队列是先进先出,符合层序遍历的顺序
2.使用一个变量cnt记录每层结点的个数,以此来控制可以刚好将同一层的结点从队列中输出。
算法:
1.创建result数组,并判空输入的根结点root,若root为null,直接返回result。
2.创建辅助队列queue,将根结点入队。
3.进入外层while循环,进行条件是队列不为空;
4.进入内层while,每一层(层是指 层次遍历的层)的循环,进行条件是cnt>0,以此来保证在while中操作的都是当前层的结点。
进入while前,设置一个变量记录当前层结点的最大值curMax,设置变量cnt存下当前层的结点个数。
在内层while中,队头结点出队,若左右孩子不为null,则左右孩子结点入队。
内层while结束后,将当前层最大值进result数组中。
2. 代码
public List<Integer> largestValues(TreeNode root) {
List<Integer> result = new ArrayList<>();
if(root == null){
return result;
}
Deque<TreeNode> queue = new ArrayDeque<>();
queue.add(root);
while (!queue.isEmpty()){
int curMax = Integer.MIN_VALUE;
int cnt = queue.size();
while(cnt-- > 0){
TreeNode node = queue.poll();
if(node.left != null){
queue.add(node.left);
}
if(node.right != null){
queue.add(node.right);
}
//更新当前层的max值
curMax = node.val > curMax ? node.val : curMax;
}
result.add(curMax);
}
return result;
}
[No.vi] 116. 填充每个节点的下一个右侧节点指针
1. 思路
1.俺の思路
层序遍历并能区分每层的思路:
1.借用队列。因为队列是先进先出,符合层序遍历的顺序
2.使用一个变量cnt记录每层结点的个数,以此来控制可以刚好将同一层的结点从队列中输出。
算法:
1.创建辅助队列queue,将根结点入队。
2.进入外层while循环,进行条件是队列不为空;
3.进入内层while,每一层(层是指 层次遍历的层)的循环,进行条件是cnt>0,以此来保证在while中操作的都是当前层的结点。
进入while前,先出队一个结点,记为前继结点frontNode。设置变量cnt存下当前层的结点个数。
在内层while中,若frontNode左右孩子不为null,则左右孩子结点入队。若当前层剩余结点数为0,即cnt==0,则frontNode为当前层最后一个结点,将其next指为null,break;开启下一层的循环。若当前层还有结点curNode,则出队,frontNode指向curNode,更新frontNode = curNode
4.最后返回root
2. 代码
public Node connect(Node root) {
if(root == null){
return root;
}
Deque<Node> queue = new ArrayDeque<>();
queue.add(root);
while(!queue.isEmpty()){
Node frontNode = queue.poll();
int cnt = queue.size();
//开始每层的操作
while(cnt >= 0){
if(frontNode.left != null){
queue.add(frontNode.left);
}
if(frontNode.right != null){
queue.add(frontNode.right);
}
//每层最后一个结点的next指向null
if(cnt == 0){
frontNode.next = null;
break;
}else {
Node curNode = queue.poll();
frontNode.next = curNode;
frontNode = curNode;
--cnt;
}
}
}
return root;
}
[No.vii] 117. 填充每个节点的下一个右侧节点指针 II
1. 思路
0.与上一题的区别。
上一题是完全二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。
这一题是二叉树,所以不一定有两个节点
代码一样滴
[No.viii] 104. 二叉树的最大深度
1. 思路
按第一题模版例题的思路,只不过再内层循环不需要做什么操作,只是在内层循环结束时,++depth;
在外层循环结束时,返回depth
2. 代码
public int maxDepth(TreeNode root) {
if(root == null){
return 0;
}
int maxDepth = 0;
Deque<TreeNode> queue = new ArrayDeque<>();
queue.add(root);
while (!queue.isEmpty()){
int cnt = queue.size();
while(cnt-- > 0){
TreeNode node = queue.poll();
if(node.left != null){
queue.add(node.left);
}
if(node.right != null){
queue.add(node.right);
}
}
//一层结束
++maxDepth;
}
return maxDepth;
}
[No.viii] 111. 二叉树的最小深度
1. 思路
按第一题模版例题的思路,只不过再内层循环不需要做什么操作,只是在内层循环结束时,++depth;并且若有结点左右孩子都为null,就return;
以及在外层循环结束时,return depth。
2. 代码
public int minDepth(TreeNode root) {
if(root == null){
return 0;
}
int min = 1;
Deque<TreeNode> queue = new ArrayDeque<>();
queue.add(root);
while(!queue.isEmpty()){
int cnt = queue.size();
while(cnt-- > 0){
TreeNode node = queue.poll();
if(node.left == null && node.right == null){
return min;
}
if(node.left != null){
queue.add(node.left);
}
if(node.right != null){
queue.add(node.right);
}
}
++min;
}
return min;
}
(三) 226.翻转二叉树 (优先掌握递归)
这道题目 一些做过的同学 理解的也不够深入,建议大家先看我的视频讲解,无论做过没做过,都会有很大收获。
1. 思路
1.递归:推荐前序、后序
发现只要把每一个节点的左右孩子翻转一下,就可以达到整体翻转的效果。
因为可以拆分为相同的细小的都是左右孩子翻转的这个动作,所以可以使用递归。
前序和后序思路较顺;中序也可以但有点绕。(以下连续的数字为层序遍历)
中序:遍历顺序,左中右。走到1,return到2,2的两个孩子翻转,来到4。4的两个孩子翻转,此时2为根的小树来到了右侧,根据中序遍历的顺序,左根右,现在又要调换右侧根为2的小树了。此时根为2的小树,其左右孩子被调换两次了;根为7的小树的左右孩子就没被调换过。所以,有点绕的是,遍历递归代码要写为左中左了。因为经历’中’之后,左右的位置已经互换了,要想找‘右’,就得找‘左’。
前序:根左右。4先调换,此时4 72 6913。调换7,此时4 72 9613。再走到7的两个孩子69,他们是叶子结点不用调换孩子return。调换2,此时4 72 9631。再2的两个叶子结点,没有孩子,最终return。
后序:从叶子结点依次调换到根结点4.
2.复习递归三部曲
1)确定递归函数的参数和返回值
参数就是要传入节点的指针,不需要其他参数了,通常此时定下来主要参数,如果在写递归的逻辑中发现还需要其他参数的时候,随时补充。
返回值的话其实也不需要,但是题目中给出的要返回root节点的指针,可以直接使用题目定义好的函数,所以就函数的返回类型为TreeNode*。
2)确定终止条件
当前节点为空的时候,就返回 if (root == NULL) return root;
3)确定单层递归的逻辑
因为是先前序遍历,所以先进行交换左右孩子节点,然后反转左子树,反转右子树。
2. 代码
public TreeNode invertTree(TreeNode root) {
if(root == null){
return root;
}
//前序遍历(先根结点交换左右孩子,再操作(反转)左右子树)
swapNode(root);
invertTree(root.left);
invertTree(root.right);
return root;
}
private void swapNode(TreeNode node){
TreeNode temp = node.left;
node.left = node.right;
node.right = temp;
}
3. 实现过程中的问题
这样交换的是指向两个孩子结点的指针,而没有交换两个孩子本身。
public TreeNode invertTree(TreeNode root) {
if(root == null){
return root;
}
swapNode1(root.left, root.right);
invertTree(root.left);
invertTree(root.right);
return root;
}
private void swapNode1(TreeNode left, TreeNode right){
TreeNode temp = left;
left = right;
right = temp;
}
(四)101. 对称二叉树 (优先掌握递归)
先看视频讲解,会更容易一些。
1. 思路
如上图所示,是一棵对称二叉树。那么这道题的子问题是考虑,某结点的左右孩子是否相等吗?对于结点1是这样的,但是对于结点2,发现他的孩子3,4并不相等。
所以,本题实际考察的是根结点的两个子树翻转后是否相等。即左子树的外侧是否与右子树的里侧相等。
递归三部曲
1)确定递归函数的参数和返回值
由于是要判断左右两棵子树是否对称,所以参数是左右两个子树的根结点。由于是判断是否对称,所以返回值是boolean类型。
2)确定终止条件
!注意我们比较的其实不是左孩子和右孩子,比较的是在对称位置上的点。所以如下我称之为左节点右节点。
要比较两个节点数值相不相同,首先要把两个节点为空的情况弄清楚。以此来避免出现空指针的情况。
- 左为空,右不为空。不相等,返回false
- 右为空,左不为空。不相等,返回false
- 左右都为空,相等。返回true
两节点都不为空时。
- 两结点的值不相等,返回false
- 两结点的值相等,返回true
3)确定单层递归的逻辑
只有左右两结点都不为空,且数值相等才会进入单层递归的逻辑。
比较二叉树外侧是否对称(调递归):传入的是左节点的左孩子,右节点的右孩子。//左
比较内侧是否对称(调递归),传入左节点的右孩子,右节点的左孩子。//右
如果左右都对称就返回true ,有一侧不对称就返回false 。//中
单对于左节点来说是,左右中。所以这里也看作是后序遍历。
所以通过代码也能看出只能使用后序遍历。必须内侧,外侧都比较过后,都为true,才代表根结点是true。
2. 代码
class Solution {
public boolean isSymmetric(TreeNode root) {
//根据题意至少有一个树节点,所以这里不对root判空了
return compare(root.left, root.right);
}
//递归
private boolean compare(TreeNode left, TreeNode right){
if(left == null && right == null){
return true;
}else if(left == null || right == null){
return false;
}else if(left.val != right.val){
return false;
}else {//只剩下两结点不为空,且值相等这种情况了
boolean out = compare(left.left, right.right);//比较外侧是否对称
boolean in = compare(left.right, right.left);//比较内侧是否对称
return out && in;
}
}
}