视频链接:
讲透二叉树的层序遍历 | 广度优先搜索 | LeetCode:102.二叉树的层序遍历_哔哩哔哩_bilibili
听说一位巨佬面Google被拒了,因为没写出翻转二叉树 | LeetCode:226.翻转二叉树_哔哩哔哩_bilibili
新学期要从学习二叉树开始! | LeetCode:101. 对称二叉树_哔哩哔哩_bilibili
二叉树的层次遍历就是图论中的广度优先遍历,是一种规律很足的遍历方式。我们在前一个章节学习了前序、中序、后序的遍历思想,这三种思想为我们完整获取树上各节点的值提供了可行的方法,而广度优先以及深度优先两种遍历方式便是应对树型结构其他问题的有效解题方法。深度遍历的策略非常简单,其与中序遍历思想非常接近,在涉及树的高度等题目中被广泛的使用;而广度优先遍历,是操作节点之间连接、树型结构找规律等题目中被有效的应用。掌握这两类方式对于解二叉树甚至N叉树相关的题目非常关键。代码随想录 给出了完整的层次遍历方法的递归与迭代两种实现策略,其中最为关键的操作就是应该如何获取到当前指针所处在的层次,递归中通过了传递层次deep参数实现,迭代中使用队列辅助左右子树的遍历,提前记录上一轮队列元素的size,判断当前层次的元素是否已经完全遍历。
下面给出对于代码随想录 章节十道题的求解过程。
使用迭代实现,使用数组模拟队列加快数据读取,但就时间效率来看,还是递归效率最佳。
/**
* 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;
* }
* }
*/
// 时间复杂度O(n),空间复杂度o(n)
class Solution {
public List> levelOrder(TreeNode root) {
// 采用迭代的方式实现遍历
// 构建辅助数据结构,并且采用辅助数据结构是否为空作为判断条件时,就需要先将root加入到辅助数据结构中去
TreeNode[] arr = new TreeNode[4002];
List> res = new ArrayList<>();
if(root == null)
return res;
int j = 1;
int i = 0;
arr[j++] = root;
// 这样就实现层次遍历了,那么如何来判断层次遍历到了第几层了呢?
// 答案是可以采用完全二叉树的特点来辅助判断,因为我们所看到的
// i == j就表示队列为空了
while(i < j){
int end = j;
List list = new ArrayList<>();
while(i < end){
TreeNode t = arr[i++];
if(t != null){
list.add(t.val);
arr[j++] = t.left;
arr[j++] = t.right;
}
}
if(list.size() > 0)
res.add(list); // 还是需要添加边界条件的,否则root == null时,一个空的list将会被添加至res中,造成结果为[[]]
}
return 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;
* }
* }
*/
// 时间复杂度O(n),空间复杂度O(n)
class Solution {
public List> levelOrderBottom(TreeNode root) {
List> ans = new ArrayList<>();
if(root == null)
return ans;
TreeNode[] arr = new TreeNode[4002];
int i = 0;
int j = 0;
arr[j++] = root;
while(i < j){
int end = j;
List list = new ArrayList<>();
while(i < end){
TreeNode t = arr[i++];
if(t!=null){
list.add(t.val);
arr[j++] = t.left;
arr[j++] = t.right;
}
}
if(list.size()>0)
ans.add(list);
}
Collections.reverse(ans);
return ans;
}
}
思路:右视图就是看到每一层的最后一个节点,那么使用层次遍历,定位每一层的最后一个节点输出即可。
/**
* 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;
* }
* }
*/
// 时间复杂度O(n),空间复杂度O(n)
class Solution {
public List rightSideView(TreeNode root) {
List ans = new ArrayList<>();
if(root == null)
return ans;
TreeNode[] arr = new TreeNode[202];
int i = 0;
int j = 0;
arr[j++] = root;
while(i < j){
int end = j;
System.out.println(end);
while(i < end){
TreeNode t = arr[i++];
if(t.left != null)
arr[j++] = t.left;
if(t.right != null)
arr[j++] = t.right;
}
if(end > 0){
TreeNode t = arr[end-1];
ans.add(t.val);
}
}
return ans;
}
}
思路:很明确的提示了,层平均值,所以层次遍历皆可,但这题写法的时间开销大了。
/**
* 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;
* }
* }
*/
// 遍历的时间复杂度O(n),空间复杂度O(n),但是最后计算均值的时间复杂度来到了o(n^2)
class Solution {
public List averageOfLevels(TreeNode root) {
List> ans = new ArrayList<>();
List res = new ArrayList<>();
if(root == null)
return res;
BFS(root, 1, ans);
for(int i=0; i> ans){
// 停止条件
if(t==null)
return;
// 非常想的是能否在递归中去实现同一个floor内的元素之间的相加减,但是层次遍历中不同floor的操作并不是相邻的进行,所以暂时认为还是需要将所有的值都获取到了之后再求取平均
if(ans.size() < floor){
List list = new ArrayList<>();
ans.add(list);
}
ans.get(floor-1).add(t.val);
// 当前树节点 t 操作完成
BFS(t.left, floor+1, ans);
BFS(t.right, floor+1, ans);
}
}
将访问左右子树变为训练遍历即可
/*
// Definition for a Node.
class Node {
public int val;
public List children;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, List _children) {
val = _val;
children = _children;
}
};
*/
// 时间复杂度O(n),空间复杂度O(n)
class Solution {
public List> levelOrder(Node root) {
List> ans = new ArrayList<>();
if(root == null)
return ans;
BFS(root, 1, ans);
return ans;
}
// 递归大法真是好
public void BFS(Node t, int floor, List> ans){
if(t==null)
return;
if(ans.size() < floor){
List list = new ArrayList<>();
list.add(t.val);
ans.add(list);
}
else
ans.get(floor-1).add(t.val);
for(int i=0; i
思路:层次遍历对每一行元素进行比较,找其中的最大值;而传统层次遍历中需要对每个元素进行记录,也可以直接改进为只记录最大值,加快遍历速度。
/**
* 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;
* }
* }
*/
// 时间复杂度O(n),空间复杂度O(n)
class Solution {
public List largestValues(TreeNode root) {
List ans = new ArrayList<>();
if(root == null)
return ans;
BFS(root, 1, ans);
return ans;
}
public void BFS(TreeNode t, int floor, List ans){
if(t == null)
return;
if(ans.size() < floor)
ans.add(Integer.MIN_VALUE);
if(t.val>ans.get(floor-1))
ans.set(floor-1, t.val);
BFS(t.left, floor+1, ans);
BFS(t.right, floor+1, ans);
}
}
思路:采用的迭代的策略不是这题最佳的题解,我认为使用当前层次操作下一层次的策略才是最好的解法。
/*
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node next;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, Node _left, Node _right, Node _next) {
val = _val;
left = _left;
right = _right;
next = _next;
}
};
*/
// 时间复杂度O(n),空间复杂度O(n)
class Solution {
public Node connect(Node root) {
List> cur = new ArrayList<>();
BFS(root, 1, cur);
return root;
}
public void BFS(Node t, int floor, List> cur){
if(t==null)
return;
if(cur.size() < floor){
List list = new ArrayList<>();
list.add(t);
cur.add(list);
}
else{
cur.get(floor-1).get(cur.get(floor-1).size()-1).next = t;
cur.get(floor-1).add(t);
}
BFS(t.left, floor+1, cur);
BFS(t.right, floor+1, cur);
}
// public Node connect(Node root) {
// if(root == null) return root;
// Node pre = root;
// while(pre.left != null) {
// Node temp = pre;
// while(temp != null) {
// temp.left.next = temp.right;
// if(temp.next != null) temp.right.next = temp.next.left;
// temp = temp.next;
// }
// pre = pre.left;
// }
// return root;
// }
}
思路:这道题从题意上理解来看,原以为是所有节点的next必须指向右侧最近某个结点的right部位,但从解题上来看,那么如果是一棵完全二叉树的话,那么中间部分的某棵子树的right就要指向其最近右兄弟的right,而忽略其右兄弟的left节点,挺怪异的。就如下图这样。
但其实题目不是这个意思,因为从解题层面来看,迭代的思想,出队当前元素后, 你无法定位下一个出队的元素是某个节点的left还是right,那么next指针就指不上去;如果采用递归来写的话,也是一样,就是当前节点的目标右节点,即某个节点的right部分没办法确定,完全二叉树是间隔两个位置,那么其他二叉树可以当前节点到下一个兄弟之间都没有节点,但是间隔很远,但是兄弟是left还是right你没法确定,或者写了if进行判断,甚至在队列内存了键值对,也可以达到判断的目的,但非常繁琐。另外第三个原因是,如果是上图这样的结果的话,那么next指针之间将会断裂,存在两个节点的next指向了同一个节点,是非常有问题的。
所以纵观题意与给出的测试用例,本题与116 题的区别就是在于116是满二叉树(就是题目中的完美二叉树,但不是完全二叉树),而本题就是一颗普通二叉树,需要在这颗普通二叉树中将所有节点的next指向其最近的右侧兄弟。
/*
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node next;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, Node _left, Node _right, Node _next) {
val = _val;
left = _left;
right = _right;
next = _next;
}
};
*/
// 时间复杂度O(n),空间复杂度O(n)
class Solution {
public Node connect(Node root) {
if(root == null)
return root;
Queue queue = new LinkedList<>();
queue.offer(root);
while(queue.size()>0){
// 每一次从队列首部取出当前层次的元素
// 如果采用迭代法实现本题,那么就需要获取当前列表的长度,用来表示操作当前层次
int size = queue.size();
int i = 0;
Node pre = null;
while(i < size){
Node t = queue.poll();
// 每一层的第一个节点仅让pre指向其即可,然后当t到了第二个元素的时候,就可以让pre的next指向t开始完成链接
if(i == 0)
pre = t;
else{
pre.next = t;
pre = t;
}
// 压入新的元素
if(t.left != null)
queue.add(t.left);
if(t.right != null)
queue.add(t.right);
i++;
}
}
return 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;
* }
* }
*/
// 时间复杂度O(n),空间复杂度O(n)
class Solution {
public int maxDepth(TreeNode root) {
return DFS(root);
}
public int DFS(TreeNode t){
if(t==null)
return 0;
int left = DFS(t.left)+1;
int right = DFS(t.right)+1;
return left>right?left: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;
* }
* }
*/
// 时间复杂度O(n),空间复杂度O(n)
class Solution {
public int min_height = Integer.MAX_VALUE;
public int minDepth(TreeNode root) {
if(root == null)
return 0;
BFS(root, 1);
return min_height;
}
public void BFS(TreeNode t, int floor){
if(t == null)
return;
// 寻找层次值最小的一个叶子节点作为结果
if(t.left == null && t.right == null){
min_height = floor
226. 翻转二叉树
思路:翻转二叉树就是翻转左右子树。采用自顶向下交换左右子树还是自底向上交换左右子树皆可,对应的就是前序与后序的遍历。中序遍历一定需要结合图来观察交换之后树的形状,中序遍历两次翻转子树都是操作的同一边。
/**
* 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;
* }
* }
*/
// 时间复杂度O(n),空间复杂度O(n)
class Solution {
public TreeNode invertTree(TreeNode root) {
inOrderTraversal(root);
return root;
}
public void inOrderTraversal(TreeNode t){
if(t == null)
return;
TreeNode temp = t.left;
t.left = t.right;
t.right = temp;
if(t.left != null)
inOrderTraversal(t.left);
if(t.right != null)
inOrderTraversal(t.right);
}
}
思路:是采用双遍历的方式,比较左右子树的外侧节点是否相等,内侧节点是否相等。主要这道题的题意个人认为给出并明确,这里的对称是元素与树型结构的双对称,因此在判断left和right的时候,left与right同为null也是一种可信的对称,包括root为null,也是对称。
这里采用后序遍历的思想实现解题,原因是判断是否相等的操作需要收集左右孩子的信息,因此仅有后序遍历的做法贴合。
/**
* 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;
* }
* }
*/
// 时间复杂度O(n),空间复杂度O(n)
class Solution {
int i = 0;
public boolean isSymmetric(TreeNode root) {
if(root == null)
return true;
return postOrderTraversal(root.left, root.right);
}
public boolean postOrderTraversal(TreeNode left, TreeNode right){
if(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;
return postOrderTraversal(left.left, right.right) && postOrderTraversal(left.right, right.left);
}
}