层序遍历
层序遍历 即广度优先遍历,同样有递归、迭代两种解法:
递归
思路
面对递归思路,明确三点,一递归函数的参数与返回值 二递归终止条件 三确定单层递归的处理
对于层序遍历,每次递归添加某层的所有元素,当当前节点为null return 参数携带deep以确定结果二维数组中的层数,因此参数为 node 与 deep。每次递归的处理流程为 先判断终止条件,然后deep+1 处理resList长度问题,最后将当前节点对应的val加入resList 后按照左-右顺序进行递归。
代码
class Solution {
public List<List<Integer>> resList = new ArrayList<List<Integer>>();
public List<List<Integer>> levelOrder(TreeNode root) {
level(root, 0);
return resList;
}
public void level(TreeNode root, int deep){
//确定递归终止条件
if(root == null){
return;
}
//层数加1
deep++;
while(resList.size() < deep){
resList.add(new ArrayList<Integer>());
}
//当前节点对应的resList下标为deep - 1
resList.get(deep - 1).add(root.val);
level(root.left, deep);
level(root.right, deep);
}
}
迭代
思路
利用队列先进先出的特性,存放每层的节点,然后从左向右依次将节点的值加入一个数组,在每层遍历结束后 将数组加入resList
代码
class Solution {
public List<List<Integer>> resList = new ArrayList<List<Integer>>();
public List<List<Integer>> levelOrder(TreeNode root) {
if(root == null){
return resList;
}
Queue<TreeNode> que = new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
List<Integer> item = new ArrayList<>();
//遍历当前que内的元素
int len = que.size();
while(len > 0){
TreeNode temp = que.poll();
item.add(temp.val);
if(temp.left != null){
que.offer(temp.left);
}
if(temp.right != null){
que.offer(temp.right);
}
len--;
}
resList.add(item);
}
return resList;
}
}
102.二叉树的层序遍历
给你二叉树的根节点 root
,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
示例 1:
输入:root = [3,9,20,null,null,15,7] 输出:[[3],[9,20],[15,7]]
示例 2:
输入:root = [1] 输出:[[1]]
示例 3:
输入:root = [] 输出:[]
递归 / 迭代 代码见上
107.二叉树的层序遍历Ⅱ
给你二叉树的根节点 root
,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
思路
与102非常类似,只需反转结果数组即可
递归
class Solution {
public List<List<Integer>> resList = new ArrayList<List<Integer>>();
public List<List<Integer>> levelOrderBottom(TreeNode root) {
levelBottom(root, 0);
Collections.reverse(resList);
return resList;
}
public void levelBottom(TreeNode root, int deep){
//递归终止条件
if(root == null){
return;
}
deep++;
while(resList.size() < deep){
resList.add(new ArrayList<Integer>());
}
resList.get(deep - 1).add(root.val);
levelBottom(root.left, deep);
levelBottom(root.right, deep);
}
}
迭代
class Solution {
public List<List<Integer>> resList = new ArrayList<List<Integer>>();
public List<List<Integer>> levelOrderBottom(TreeNode root) {
if(root == null){
return resList;
}
Queue<TreeNode> que = new LinkedList<TreeNode>();
que.offer(root);
while(!que.isEmpty()){
List<Integer> item = new ArrayList<Integer>();
int len = que.size();
//遍历当前层的所有节点
while(len > 0){
TreeNode temp = que.poll();
item.add(temp.val);
if(temp.left != null) que.offer(temp.left);
if(temp.right != null) que.offer(temp.right);
len--;
}
resList.add(item);
}
Collections.reverse(resList);
return resList;
}
}
199.二叉树的右视图
给定一个二叉树的 根节点 root
,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
思路
同样的,递归/迭代,递归完成后只加入resList每个数组元素的最后一个值
迭代在进行中时 添加最后一个值即可
递归
class Solution {
public List<List<Integer>> resList = new ArrayList<>();
public List<Integer> rightSideView(TreeNode root) {
rightSide(root, 0);
List<Integer> res = new ArrayList<Integer>();
for(List<Integer> list : resList){
res.add(list.get(list.size()-1));
}
return res;
}
public void rightSide(TreeNode root, int deep){
if(root == null){
return;
}
deep++;
while(resList.size() < deep){
resList.add(new ArrayList<Integer>());
}
resList.get(deep-1).add(root.val);
rightSide(root.left, deep);
rightSide(root.right, deep);
}
}
迭代
注意在遍历每层开始前保存 每层的节点数量(len) 否则会无法存放相应的数值
class Solution {
public List<Integer> rightSideView(TreeNode root) {
List<Integer> res = new ArrayList<>();
Queue<TreeNode> que = new LinkedList<>();
if (root == null) {
return res;
}
que.offer(root);
while(!que.isEmpty()){
int len = que.size();
for(int i=0; i<len;i++){
TreeNode temp = que.poll();
if(temp.left != null){
que.offer(temp.left);
}
if(temp.right != null){
que.offer(temp.right);
}
if(i == len-1){
res.add(temp.val);
}
}
}
return res;
}
}
637.二叉树的层平均值
给定一个非空二叉树的根节点 root
, 以数组的形式返回每一层节点的平均值。与实际答案相差 10-5
以内的答案可以被接受。
思路
递归/迭代均可
代码
递归
class Solution {
public List<List<Integer>> resList = new ArrayList<>();
public List<Double> averageOfLevels(TreeNode root) {
List<Double> res = new ArrayList<>();
average(root, 0);
for(List<Integer> list : resList){
double sum = 0;
for(int val : list){
sum += val;
}
res.add(sum / list.size());
}
return res;
}
public void average(TreeNode root,int deep){
if(root == null){
return;
}
deep ++;
while(resList.size() < deep){
resList.add(new ArrayList<Integer>());
}
resList.get(deep - 1).add(root.val);
average(root.left, deep);
average(root.right, deep);
}
}
迭代
class Solution {
public List<Double> averageOfLevels(TreeNode root) {
List<Double> res = new ArrayList<>();
if(root == null){
return res;
}
Queue<TreeNode> que = new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
int len = que.size();
double sum = 0;
for(int i = 0; i < len; i++){
TreeNode temp = que.poll();
sum += temp.val;
if(temp.left != null) que.offer(temp.left);
if(temp.right != null) que.offer(temp.right);
}
res.add(sum/len);
}
return res;
}
}
429.N叉树的层序遍历
思路与上述完全类似
递归
class Solution {
public List<List<Integer>> resList = new ArrayList<>();
public List<List<Integer>> levelOrder(Node root) {
level1(root, 0);
return resList;
}
public void level1(Node root, int deep){
if(root == null){
return;
}
deep++;
while(resList.size() < deep){
resList.add(new ArrayList<Integer>());
}
resList.get(deep - 1).add(root.val);
for(Node no : root.children){
level1(no, deep);
}
}
}
迭代
class Solution {
public List<List<Integer>> resList = new ArrayList<>();
public List<List<Integer>> levelOrder(Node root) {
if(root == null){
return resList;
}
Queue<Node> que = new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
List<Integer> item = new ArrayList<>();
int len = que.size();
for(int i=0; i < len; i++){
Node node = que.poll();
item.add(node.val);
for(Node no : node.children){
if(no != null) que.offer(no);
}
}
resList.add(item);
}
return resList;
}
}
515.在每个树行中找最大值
给定一棵二叉树的根节点 root
,请找出该二叉树中每一层的最大值。
思路
完全类似,递归法(求得resList后再计算max)不再重复
迭代法
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;
for(int i = 0; i < len; i++){
TreeNode t = que.poll();
max = max > t.val ? max : t.val;
if(t.left != null) que.offer(t.left);
if(t.right != null) que.offer(t.right);
}
res.add(max);
}
return res;
}
}
116.填充每个节点的下一个右侧节点指针
给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
struct Node { int val; Node *left; Node *right; Node *next; }
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL
。
初始状态下,所有 next 指针都被设置为 NULL
。
示例 1:
输入:root = [1,2,3,4,5,6,7] 输出:[1,#,2,3,#,4,5,6,7,#] 解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。序列化的输出按层序遍历排列,同一层节点由 next 指针连接,'#' 标志着每一层的结束。
示例 2:
输入:root = [] 输出:[]
思路
迭代,在遍历每层的节点时 将本节点的next指向当前队列顶即可
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();
for(int i=0; i<len; i++){
Node cur = que.poll();
if(i < len-1){
Node next = que.peek();
cur.next = next;
}else{
cur.next = null;
}
if(cur.left != null) que.offer(cur.left);
if(cur.right != null) que.offer(cur.right);
}
}
return root;
}
}
117.填充每个节点的下一个右侧节点指针II
给定一个二叉树:
struct Node { int val; Node *left; Node *right; Node *next; }
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL
。
初始状态下,所有 next 指针都被设置为 NULL
。
思路
这道题目说是二叉树,但116题目说是完整二叉树,其实没有任何差别,一样的代码一样的逻辑一样的味道。
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();
for(int i=0; i<len; i++){
Node cur = que.poll();
if(i < len-1){
Node next = que.peek();
cur.next = next;
}else{
cur.next = null;
}
if(cur.left != null) que.offer(cur.left);
if(cur.right != null) que.offer(cur.right);
}
}
return root;
}
}
104.二叉树的最大深度
给定一个二叉树 root
,返回其最大深度。
二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
示例 1:
输入:root = [3,9,20,null,null,15,7] 输出:3
示例 2:
输入:root = [1,null,2] 输出:2
思路
依旧是迭代解决层次遍历
class Solution {
public int maxDepth(TreeNode root) {
if(root == null){
return 0;
}
Queue<TreeNode> que = new LinkedList<>();
que.offer(root);
int deep = 0;
while(!que.isEmpty()){
deep++;
int len = que.size();
for(int i = 0; i < len; i++){
TreeNode t = que.poll();
if(t.left != null) que.offer(t.left);
if(t.right != null) que.offer(t.right);
}
}
return deep;
}
}
111.二叉树的最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。
示例 1:
输入:root = [3,9,20,null,null,15,7] 输出:2
示例 2:
输入:root = [2,null,3,null,4,null,5,null,6] 输出:5
思路
判断得到最小深度 需要某个节点左右均为null 即可
class Solution {
public int minDepth(TreeNode root) {
if(root == null){
return 0;
}
int deep = 0;
Queue<TreeNode> que = new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
int len = que.size();
deep++;
for(int i=0; i<len; i++){
TreeNode t = que.poll();
if(t.left == null && t.right == null){
return deep;
}
if(t.left != null) que.offer(t.left);
if(t.right != null) que.offer(t.right);
}
}
return deep;
}
}
226.翻转二叉树
给你一棵二叉树的根节点 root
,翻转这棵二叉树,并返回其根节点。
思路
dfs/bfs均可以解决这一问题,其中 dfs 前/后序遍历均可 中序遍历会导致部分左/右节点翻转两次
bfs就没什么可说的了
bfs/dfs均有递归与迭代两种方法
dfs
递归
class Solution {
public TreeNode invertTree(TreeNode root) {
reverse(root);
return root;
}
public TreeNode reverse(TreeNode root){
if(root == null){
return root;
}
TreeNode left = root.left;
TreeNode right = root.right;
TreeNode temp = left;
root.left = right;
root.right = temp;
//中
reverse(root.left); //左
reverse(root.right); //右
return root;
}
}
迭代
class Solution {
public TreeNode invertTree(TreeNode root) {
Stack<TreeNode> st = new Stack<TreeNode>();
st.push(root);
while(!st.isEmpty()){
//中
TreeNode t = st.pop();
TreeNode tl = t.left;
TreeNode tr = t.right;
t.right = tl;
t.left = tr;
//左
if(t.left != null) st.push(t.left);
//右
if(t.right != null) st.push(t.right);
}
return root;
}
}
bfs 层序遍历
递归
发现 bfs递归写法与dfs 非常类似 区别?
class Solution {
public TreeNode invertTree(TreeNode root) {
invert(root);
return root;
}
public void invert(TreeNode root){
if(root == null){
return ;
}
TreeNode rl = root.left;
TreeNode rr = root.right;
root.left = rr;
root.right = rl;
invert(root.left);
invert(root.right);
}
}
迭代
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root == null){
return root;
}
Queue<TreeNode> que = new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
int len = que.size();
for(int i=0; i<len; i++){
TreeNode t = que.poll();
TreeNode tl = t.left;
t.left = t.right;
t.right = tl;
if(t.left != null) que.offer(t.left);
if(t.right != null) que.offer(t.right);
}
}
return root;
}
}
101. 对称二叉树
给你一个二叉树的根节点 root
, 检查它是否轴对称。
思路
递归
对于二叉树是否对称,其实是比较左、右子树是不是互相翻转的,理解这一点就知道了其实我们要比较的是两个树(这两个树是根节点的左右子树),所以在递归遍历的过程中,也是要同时遍历两棵树。
那么如何比较呢?
比较的是两个子树的里侧和外侧的元素是否相等。
那么遍历的顺序应该是怎样的呢?
本题只能后序遍历 因为需要通过递归函数的返回值来判断两个子树的内侧节点和外侧节点是否相等。
正是因为要遍历两棵树而且要比较内侧和外侧节点,所以准确的来说是一个树的遍历顺序是左右中,一个树的遍历顺序是右左中。
但都可以理解算是后序遍历,尽管已经不是严格上在一个树上进行遍历的后序遍历了。
通过上述说明
确定递归函数的三步曲:
1、确定递归函数的参数与返回值
递归函数的参数需要两颗树,然后通过其返回值说明这两棵树是否对称,返回值为boolean
2、确定递归终止条件
根据左、右子树的内测、外侧来判断是否对称 进而决定递归函数的终止条件
3、确定单次递归的操作
单层递归的逻辑就是处理 左右节点都不为空,且数值相同的情况
比较外侧、内测、
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root == null){
return true;
}
return judge(root.left, root.right);
}
//确定递归函数的参数与返回值
public boolean judge(TreeNode left, TreeNode right){
//终止条件
if(left == null && right != null) return false;
else if(left != null && right == null) return false;
else if(right == null && left == null) return true;
else if(left.val != right.val) return false;
//此时余下的情况便是左右非空 值相等 此时递归查看
//即确定单层递归的逻辑
//此时才进入单层递归的逻辑,单层递归的逻辑就是处理 左右节点都不为空,且数值相同的情况。
boolean j1 = judge(left.left,right.right);
boolean j2 = judge(left.right, right.left);
return j1 && j2;
}
}
迭代
使用队列/栈 来判断左右子树对应节点是否相等,放入根节点的左、右子树 然后成对取出即可。
注意放入时的顺序对应
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root == null){
return true;
}
Queue<TreeNode> que = new LinkedList<>();
que.offer(root.left);
que.offer(root.right);
while(!que.isEmpty()){
TreeNode left = que.poll();
TreeNode right = que.poll();
if(left == null && right == null){
//左右均空
continue;
}
else if(left == null || right == null || left.val != right.val){
return false;
}
que.offer(left.left);
que.offer(right.right);
que.offer(left.right);
que.offer(right.left);
}
return true;
}
}