这篇主要内容是二叉树层序遍历可以解决力扣的哪些题目,在已经明白层序遍历相关原理的基础上去刷题。
层序遍历的主要思想需要借助队列先进先出的特性来存储遍历到的结点的左右孩子。
具体思想是:最开始将根节点加入队列,然后遍历队列,如果队列不为空,将队头结点出队,将队头结点值加入res中,并且将当前出队的结点的左右孩子加入队列;继续遍历队列,队列不为空的时候,重复出队头、加入res、左右孩子入队的操作;这样可以保证遍历二叉树的时候,永远都是在最上层的结点先被访问到(因为先出队的才能先被访问到)。
//借助队列实现层序遍历
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res=new ArrayList<>();//res存储结果,注意题目的结果的格式(res是由许多resItem组成);
if(root==null)return res;//空树返回空结果
Queue<TreeNode> que=new LinkedList<>();//借助队列实现层序遍历,que继承LinkedList类(可以使用里面的方法)
que.offer(root);//入队操作(队尾入队),add方法出错会抛出异常,offer只是返回布尔值,所以用offer方法
//外层的while用来遍历整个队列(输出到res中)
while(!que.isEmpty()){
int len=que.size();//len用来记录当前遍历到的这一层有多少结点
List<Integer> resItem =new ArrayList<>();//每个resItem存这一层的结点值
//内层的while用来遍历每一层的结点(输出到resItem)
while(len-->0){//遍历这一层的结点,遍历完len就到0
TreeNode cur=que.poll();//poll获取队首元素并且出队
resItem.add(cur.val);//结点值加入这一层的结果中
if(cur.left!=null)que.offer(cur.left);//左右孩子入队
if(cur.right!=null)que.offer(cur.right);
}
res.add(resItem);//一层遍历结束,这一层的结点值加入最终结果中
}
return res;
}
}
这题要求返回自底向上的层次遍历结果,只要在102题的基础上把res翻转就行了,在java里面可以用Collections.reverse(res),效果就相当于输出从底层到顶层的层序结果。
class Solution {
public List<List<Integer>> levelOrderBottom(TreeNode root) {
List<List<Integer>> res=new ArrayList<>();
if(root==null)return res;
Queue<TreeNode> que=new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
int len=que.size();
List<Integer> resItem= new ArrayList<>();
while(len-->0){
TreeNode cur=que.poll();
resItem.add(cur.val);
if(cur.left!=null)que.offer(cur.left);
if(cur.right!=null)que.offer(cur.right);
}
res.add(resItem);
}
Collections.reverse(res);
return res;
}
}
这题要求输出给定二叉树的最大层数,同样可以用层序遍历的思想解决,只要在外层while循环里面加一个depth用来记录层数即可。
class Solution {
public int maxDepth(TreeNode root) {
if(root==null)return 0;
int depth=0;//记录当前遍历的深度
Queue<TreeNode> que=new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
int len=que.size();
depth++;//外层while遍历所有层
while(len-->0){//内层while循环用来遍历这一层的所有结点
TreeNode cur=que.poll();
if(cur.left!=null)que.offer(cur.left);
if(cur.right!=null)que.offer(cur.right);
}
}
return depth;
}
}
这题要求的是最小的深度,只要在104基础上加一个判断就行。在内层while循环中,若当前结点没有左右孩子,说明它是叶子结点,这里就是最小深度。
class Solution {
public int minDepth(TreeNode root) {
if(root==null)return 0;
int depth=0;
Queue<TreeNode> que=new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
int len=que.size();
depth++;
while(len-->0){
TreeNode cur=que.poll();
if(cur.left!=null)que.offer(cur.left);
if(cur.right!=null)que.offer(cur.right);
//如果当前结点无左右孩子,说明它是叶子结点,则此层有最小深度
if(cur.left==null&&cur.right==null)return depth;
}
}
return depth;//如果不会提前返回,说明会一直到最后一层
}
}
这题要求输出每一层的最右边的结点值,只要在内层while循环里面判断是不是遍历到了这一层最后一个结点,如果是,把结点值加入res。
class Solution {
public List<Integer> rightSideView(TreeNode root) {
List<Integer> res =new ArrayList<>();
if(root==null)return res;
Queue<TreeNode> que = new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
int len=que.size();
while(len-->0){
TreeNode cur= que.poll();
if(len==0)res.add(cur.val);//当len=0时,说明这一层遍历到了最后一个结点
if(cur.left!=null)que.offer(cur.left);
if(cur.right!=null)que.offer(cur.right);
}
}
return res;
}
}
这题要求输出每一层的结点值的平均值,直接在层序遍历过程中计算即可。
class Solution {
public List<Double> averageOfLevels(TreeNode root) {
List<Double> res =new ArrayList<>();
Queue<TreeNode> que = new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
int len=que.size();
double sum=0;//sum用于累加每一层的结点值的总和
for(int i=0;i<len;i++){//这里用while不太方便,因为后面还需要用到len的值,不好用while(len-->0)
TreeNode cur= que.poll();
sum+=cur.val;
if(cur.left!=null)que.offer(cur.left);
if(cur.right!=null)que.offer(cur.right);
}
res.add(sum/len);//该层遍历结束,求平均值
}
return res;
}
}
要求输出n叉树的层序遍历结果,与二叉树层序思路一样。
class Solution {
public List<List<Integer>> levelOrder(Node root) {
List<List<Integer>> res =new ArrayList<>();
if(root==null)return res;;
Queue<Node> que=new LinkedList<>();//注意队列中的结点类型根据题意设置
que.offer(root);
while(!que.isEmpty()){
List<Integer> resItem=new ArrayList<>();
int len=que.size();
while(len-->0){
Node cur=que.poll();
resItem.add(cur.val);
//n叉树只要在内层while循环时,相比二叉树访问左右孩子,把自己孩子访问完就行
for(Node child:cur.children)que.offer(child);
}
res.add(resItem);
}
return res;
}
}
要求找出二叉树每一层中的最大值,只要在层序遍历过程中更新每层最大值,在一层遍历结束后将最大值加入res中即可。
class Solution {
public List<Integer> largestValues(TreeNode root) {
List<Integer> res =new ArrayList<>();
if(root==null)return res;
Queue<TreeNode> que= new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
int len=que.size();
int max=que.peek().val;//每一层遍历开始前,先把最大值设为队头结点的值
while(len-->0){
TreeNode cur=que.poll();
if(cur.val>max)max=cur.val;//如果遍历过程碰到更大值,就更新
if(cur.left!=null)que.offer(cur.left);
if(cur.right!=null)que.offer(cur.right);
}
res.add(max);
}
return res;
}
}
要求将满二叉树每一层的结点用next从左到右串联;每个结点有val、next指针、left和right指针,初始next全置null;
解法1:
不考虑空间效率,可以用层序遍历的方法,空间复杂度O(n);也可以使用递归的方法,题目中说递归的调用栈不算,也可以粗略认为使用常数级空间;
//层序遍历方法(O(n)空间复杂度)
class Solution {
public Node connect(Node root) {
if(root==null)return root;
Queue<Node> que=new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
int len=que.size();
while(len-->0){
Node cur=que.poll();
if(len>0)cur.next=que.peek();//len=0时,cur为该层最后一个结点(本身就指向null),不用管;所以只要处理len>0的情况就行
if(cur.left!=null)que.offer(cur.left);
if(cur.right!=null)que.offer(cur.right);
}
}
return root;
}
}
//递归方法1
class Solution {
public Node connect(Node root) {
if(root==null)return root;
connectTwoNode(root.left,root.right);
return root;
}
public void connectTwoNode(Node leftNode,Node rightNode){
if(leftNode==null&&rightNode==null)return;
leftNode.next=rightNode;
connectTwoNode(leftNode.left,leftNode.right);
connectTwoNode(rightNode.left,rightNode.right);//连接右孩子的左右子结点
connectTwoNode(leftNode.right,rightNode.left);//连接左孩子的右子结点与右孩子的左子结点
}
}
//递归方法2
class Solution {
public Node connect(Node root) {
if(root==null)return null;
if(root.left!=null){
//到这里可以保证root.left不是null,就可以连接root.left与root.right
root.left.next=root.right;
//root同层有后继(右兄弟),连接root的右子结点与root右兄弟的左子结点
if(root.next!=null)root.right.next=root.next.left;
}
connect(root.left);//递归到下一层
connect(root.right);
return root;//最终返回
}
}
解法2:
想办法在常数级空间复杂度下完成next的链接,这就需要借用上一层已经建立的next指针了,也就是第N层next链接建立后,在进行第N+1层next链接的时候,会用到第N层的next域;
使用这个方法要明白满二叉树中存在两种next链接,第一种是同一父节点下的两个子结点的next链接,第二种是相邻的父节点间的子结点的链接,即第一个父节点的右孩子与第二个父节点的左孩子的链接;
//第一种
node.left.next = node.right;
//第二种
node.right.next = node.next.left
所以完整代码如下:
//常数级空间复杂度
class Solution {
public Node connect(Node root) {
if(root==null)return root;
Node mostLeft=root;//mostLeft用于记录每层的最左结点
//用mostLeft.left遍历每一层
while(mostLeft.left!=null){
Node head =mostLeft;//head用于遍历这一层的所有结点
while(head!=null){
head.left.next=head.right;//同一个父节点的子结点之间建立链接
//不同父节点的子结点建立链接
if(head.next!=null)head.right.next=head.next.left;
head=head.next;
}
mostLeft=mostLeft.left;
}
return root;
}
}
相比于上一题,这题的二叉树形没有限制(可以不是满二叉树),在不考虑空间效率前提下,可以用上题的层序遍历的方法,代码完全一样。
**·简单省事型模板解法:**空间复杂度O(n)
//层序遍历方法
class Solution {
public Node connect(Node root) {
if(root==null)return root;
Queue<Node> que=new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
int len=que.size();
while(len-->0){
Node cur=que.poll();
if(len>0)cur.next=que.peek();//len=0时,cur为该层最后一个结点(本身就指向null),不用管;所以只要处理len>0的情况就行
if(cur.left!=null)que.offer(cur.left);
if(cur.right!=null)que.offer(cur.right);
}
}
return root;
}
}
·常数级空间复杂度解法:空间复杂度O(1)
class Solution {
public Node connect(Node root) {
if (root == null)return root;
//cur我们可以把它看做是每一层的链表
Node cur = root;
//遍历当前层的时候,为了方便操作在下一层前面添加一个虚拟头结点(注意这里是访问当前层的节点,然后把下一层的节点串起来)
Node dummy = new Node();
while (cur != null) {
//tail表示访到的下一层节点,也就是已经串好的下一层链表的最后一个点
Node tail = dummy;
//然后开始遍历当前层的链表
while (cur != null) {
if (cur.left != null) {
//如果当前节点的左子节点不为空,就让tail节点的next指向他
tail.next = cur.left;
tail = tail.next;//然后再更新tail,实际上就是用tail来串这一层的点
}
if (cur.right != null) {
tail.next = cur.right;
tail = tail.next;
}
//cur遍历当前层,继续访问这一层的下一个节点
cur = cur.next;
}
//把下一层串联成一个链表之后,更新cur到下一层,直到cur为空为止
cur = dummy.next;
dummy.next=null;//断开dummy和这一层的联系,方便下层复用
}
return root;
}
}