Leetcode 刷题笔记(十三) —— 二叉树篇之二叉树的层序遍历及相关题目

文章目录

  • 系列文章目录
  • 前言
  • 题录
    • 102. 二叉树的层序遍历
    • 107. 二叉树的层序遍历 II
    • 199. 二叉树的右视图
    • 637. 二叉树的层平均值
    • 429. N 叉树的层序遍历
    • 515.在每个树行中找最大值
    • 116. 填充每个节点的下一个右侧节点指针
    • 104. 二叉树的最大深度
    • 111. 二叉树的最小深度

系列文章目录

一、 数组类型解题方法一:二分法
二、数组类型解题方法二:双指针法
三、数组类型解题方法三:滑动窗口
四、数组类型解题方法四:模拟
五、链表篇之链表的基础操作和经典题目
六、哈希表篇之经典题目
七、字符串篇之经典题目
八、字符串篇之 KMP
九、解题方法:双指针
十、栈与队列篇之经典题目
十 一、栈与队列篇之 top-K 问题
十 二、二叉树篇之二叉树的前中后序遍历
十 三、二叉树篇之二叉树的前中后序遍历
更新中 …


前言

刷题路线来自 :代码随想录
层序遍历用到 bfs(广度优先搜索) 和 dfs(深度优先搜索)。bfs 相对容易写一点,虽然 dfs 的递归写法不太好理解,但是 dfs 也很重要,变化很多。所以文章所有题目都给出了多解,也作为熟练掌握 dfs 的一个入门,dfs 的思路有点难表达清楚,这里就写了一些注释,二叉树搞定后,就出 dfs 系列题解。

题录

102. 二叉树的层序遍历

Leetcode 链接
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]
示例 2:

输入:root = [1]
输出:[[1]]
示例 3:

输入:root = []
输出:[]

题解:
bfs 使用到队列,因为队列的先进先出的特性,可以在队头结点出队的同时让孩子结点入队,队列中结点的顺序就为从上到下,从左到右的二叉树结点顺序,我们考虑的就只有什么时候出队,怎么划分每一层的结点
方法一:bfs

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<>();
        Queue<TreeNode> queue = new LinkedList<>();
        if (root != null) queue.offer(root); 
        while (!queue.isEmpty()) {
        	// 此时队列的长度就为二叉树该层结点的个数,因为在出队的时候要添加孩子结点入队,size 动态变化,需要先将长度储存起来,然后遍历
            int len = queue.size();
            List<Integer> list = new ArrayList<>();
            // 遍历该层的结点,同时让孩子结点入队
            while (len-- > 0) {
            	// 出队,让孩子结点入队
                TreeNode node =  queue.poll();
                // 出队结点 val 值添加到返回列表中
                list.add(node.val);
                // 记得判空
                if (node.left != null) queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
            }
            // 该层结点遍历结束
            res.add(list);
        }
        return res;
    }
}

方法二:dfs

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> levelOrder(TreeNode root) {
        dfs(root, 0);
        return res;
    }
    public void dfs(TreeNode root, int deep) {
        if (root == null) return;
        // 深度加一
        deep++;
        if (res.size() < deep) {
        	// 遇到本层的第一个结点,添加一个列表到返回列表中
            List<Integer> list = new ArrayList<>();
            res.add(list);
        }
        // 深度是从 1 开始,list 下标是从 0 开始,所以 res(deep-1) 对应的是第 deep 层
        res.get(deep - 1).add(root.val);
        // 向左向右搜索
        dfs(root.left, deep);
        dfs(root.right, deep);
    }
}

107. 二叉树的层序遍历 II

Leetcode 链接
给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)

示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:[[15,7],[9,20],[3]]
题解:
我们可以使用 Collections.reverse(res),但是这又遍历了一次 res,其实我们在遍历完一层节点之后,将存储该层节点值的列表添加到结果列表的头部
为了降低在结果列表的头部添加一层节点值的列表的时间复杂度,结果列表可以使用链表的结构链表头部添加一层节点值的列表的时间复杂度是 O(1)
方法一:bfs

class Solution {
    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        List<List<Integer>> res = new LinkedList<>();
        Queue<TreeNode> queue = new LinkedList<>();
        if (root != null) queue.offer(root);
        while (!queue.isEmpty()) {
            int len = queue.size();
            List<Integer> list = new ArrayList<>();
            while (len-- > 0) {
                TreeNode node = queue.poll();
                list.add(node.val);
                if (node.left != null) queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
            }
            res.add(0, list);
        }
        return res;
    }
}

方法二:dfs

class Solution {
    List<List<Integer>> res = new LinkedList<>();
    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        dfs(root, 0);
        return res;
    }
    public void dfs(TreeNode root, int deep) {
        if (root == null) return;
        deep++;
        if (res.size() < deep) {
            res.add(0, new ArrayList<>());
        }
        // 应为是到这排,倒数第 deep 层:res.size() - deep
        res.get(res.size() - deep).add(root.val);
        dfs(root.left, deep);
        dfs(root.right, deep);
    }
}

199. 二叉树的右视图

Leetcode 链接
给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

示例 1:
输入: [1,2,3,null,5,null,4]
输出: [1,3,4]

示例 2:
输入: [1,null,3]
输出: [1,3]

示例 3:
输入: []
输出: []
题解:
遍历的时候识别出是每层最后一个结点,bfs 时通过是否为最后一次循环次数判断。
深搜时,不好判断是最后一个结点,我们可以改变递归的顺序,先搜索右子树,判断是否为第一个结点即可。

方法一:bfs

class Solution {
    public List<Integer> rightSideView(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        Queue<TreeNode> queue = new ArrayDeque<>();
        if (root != null) queue.offer(root);
        while (!queue.isEmpty()) {
            int len = queue.size();
            while (len-- > 0) {
                TreeNode node =  queue.poll();
                // 每层最后一个结点
                if (len == 0) res.add(node.val);
                if (node.left != null) queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
            }
        }
        return res;
    }
}

方法二:dfs

class Solution {
    public List<Integer> rightSideView(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        dfs(res, root, 0);
        return res;
    }
    public void dfs(List<Integer> res, TreeNode root, int deep) {
        if (root == null) return;
        deep++;
        if (deep - 1 == res.size()) res.add(root.val);
        dfs(res, root.right, deep);
        dfs(res, root.left, deep);
    }
}

637. 二叉树的层平均值

Leetcode 链接
给定一个非空二叉树的根节点 root , 以数组的形式返回每一层节点的平均值。与实际答案相差 10-5 以内的答案可以被接受。

示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:[3.00000,14.50000,11.00000]
解释:第 0 层的平均值为 3,第 1 层的平均值为 14.5,第 2 层的平均值为 11 。
因此返回 [3, 14.5, 11] 。

示例 2:
输入:root = [3,9,20,15,7]
输出:[3.00000,14.50000,11.00000]
题解:
注意溢出 [2147483647,2147483647,2147483647],sum 数据类型定义为 long,或者直接定义为 double。

方法一:bfs

class Solution {
    public List<Double> averageOfLevels(TreeNode root) {
        List<Double> res = new ArrayList<>();
        Queue<TreeNode> queue = new ArrayDeque<>();
        if (root != null) queue.offer(root);
        while (!queue.isEmpty()) {
            int len = queue.size();
            double sum = 0;
            for (int i = 0; i < len; i++) {
                TreeNode node =  queue.poll();
                sum += node.val;
                if (node.left != null) queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
            }
            res.add(sum/len);
        }
        return res;
    }
}

方法二:dfs,dfs 是按深度搜索的,所以记录每层的结点个数以及和,不像 bfs 那么方便。需要每次搜索到该层时进行更新,更新的同时需要知道上次搜索到该层时的数据,所以我们使用两个 List 分别记录每层的结点个数个结点 val 和

class Solution {
    List<Double> res = new ArrayList<>();
    List<Integer> countList = new ArrayList<>();
    List<Double> sumList = new ArrayList<>();

    public List<Double> averageOfLevels(TreeNode root) {
        dfs(root, 0);
        for (int i = 0; i < countList.size(); i++) {
            res.add(sumList.get(i) / countList.get(i));
        }
        return res;
    }
    public void dfs(TreeNode root, int deep) {
        if (root == null) return;
        deep++;
        if (sumList.size() == deep - 1) {
        	// 第一次
            sumList.add((double)root.val);
            countList.add(1);
        } else {
            sumList.set(deep - 1, sumList.get(deep - 1) + root.val);
            countList.set(deep - 1, countList.get(deep - 1) + 1);
        }
        dfs(root.left, deep);
        dfs(root.right, deep);
    }
}

429. N 叉树的层序遍历

Leetcode 链接
给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例)。

示例 1:
输入:root = [1,null,3,2,4,null,5,6]
输出:[[1],[3,2,4],[5,6]]

示例 2:
输入:root = [1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14]
输出:[[1],[2,3,4,5],[6,7,8,9,10],[11,12,13],[14]]
题解:

方法一:bfs

/*
// 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;
    }
};
*/

class Solution {
    public List<List<Integer>> levelOrder(Node root) {
        List<List<Integer>> res = new ArrayList<>();
        Queue<Node> queue = new ArrayDeque<>();
        if (root != null) queue.offer(root);
        while (!queue.isEmpty()) {
            int len = queue.size();
            List<Integer> list = new ArrayList<>();
            while(len-- > 0) {
                Node node = queue.poll();
                list.add(node.val);
                // 可以使用 addAll 将孩子结点入队,也可以遍历孩子结点list,逐个入队
//                List child = node.children;
//                for (int i = 0; i < child.size(); i++) {
//                    queue.offer(child.get(i));
//                }
                queue.addAll(cur.children);
            }
            res.add(list);
        }
        return res;
    }
}

方法二:dfs

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> levelOrder(Node root) {
        dfs(root, 0);
        return res;
    }
    public void dfs(Node root, int deep) {
        if (root == null) return;
        deep++;
        if (res.size() < deep) {
            res.add(new ArrayList<>());
        }
        res.get(deep - 1).add(root.val);
        List<Node> child = root.children;
        for (int i = 0; i < child.size(); i++) {
            dfs(child.get(i), deep);
        }
    }
}

515.在每个树行中找最大值

Leetcode 链接

给定一棵二叉树的根节点 root ,请找出该二叉树中每一层的最大值。

示例1:
输入: root = [1,3,2,5,3,null,9]
输出: [1,3,9]

示例2:
输入: root = [1,2,3]
输出: [1,3]
题解:

方法一:bfs
出队时,进行最值的比较

class Solution {
    public List<Integer> largestValues(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        Queue<TreeNode> queue = new ArrayDeque<>();
        if (root != null) queue.offer(root);
        while (!queue.isEmpty()) {
            int len = queue.size();
            int tmp = Integer.MIN_VALUE;
            while (len-- > 0) {
            	// 出队并进行最值的比较
                TreeNode node = queue.poll();
                tmp = Math.max(tmp, node.val);
                if (node.left != null) queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
            }
            res.add(tmp);
        }
        return res;
    }
}

方法二:dfs

    public List<Integer> largestValues(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        dfs(res, root, 0);
        return res;
    }
    public void dfs(List<Integer> res, TreeNode root, int deep) {
        if (root == null) return;
        deep++;
        if (res.size() < deep) {
        	// 遇到每层第一个数,先添加一个最小值
            res.add(Integer.MIN_VALUE);
        }
        // 更新最值
        res.set(deep - 1 ,Math.max(res.get(deep - 1), root.val));
        dfs(res, root.left, deep);
        dfs(res, root.right, deep);
    }

116. 填充每个节点的下一个右侧节点指针

Leetcode 链接
给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:

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 = []
输出:[]
进阶:

你只能使用常量级额外空间。
使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。
题解:
方法一:bfs
在出队时将 next 指针指向队头结点

class Solution {
    public Node connect(Node root) {
        Queue<Node> queue = new ArrayDeque<>();
        if (root != null) queue.offer(root);
        while (!queue.isEmpty()) {
            int len = queue.size();
            while(len-- > 0) {
                Node node = queue.poll();
                if (len == 0) {
                    node.next = null;
                } else {
                    node.next = queue.peek();
                }
                if (node.left != null) queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
            }
        }
        return root;
    }
}

方法二:遍历上层填充下层 next 指针

class Solution {
    public Node connect(Node root) {
        if (root == null) return null;
        // 每层首结点 pre
        Node pre = root;
        while (pre.left != null) {
        	// cur 遍历上层,填充下层 next 指针
            Node cur = pre;
            while (cur != null) {
            // 填充 cur 孩子结点的 next 指针
            	// 1.左孩子 next 指向右孩子
                cur.left.next = cur.right;
                if (cur.next != null) {
                	// 2.cur 的右孩子 next 指向,cur.next 的左孩子
                    cur.right.next = cur.next.left;
                }
                // cur 向后遍历
                cur = cur.next;
            }
            // pre 向下走
            pre = pre.left;
        }
        return root;
    }
}

方法三:dfs

class Solution {
    public Node connect(Node root) {
        dfs(root);
        return root;
    }
    public void dfs(Node root) {
        if (root == null) return;
        if (root.left != null) {
        	// 有孩子结点,左孩子指向右孩子
            root.left.next = root.right;
        }
        if (root.right != null && root.next != null) {
        	// 右孩子指向下一个结点的左孩子
            root.right.next = root.next.left;
        }
        // 向左向右递归
        dfs(root.left);
        dfs(root.right);
    }
}

104. 二叉树的最大深度

Leetcode 链接
给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。

示例:
给定二叉树 [3,9,20,null,null,15,7],
返回它的最大深度 3 。
题解:
方法一:dfs 向下递归时比较最大深度

class Solution {
    public int maxDepth(TreeNode root) {
        return dfs(root, 0);
    }
    public int dfs(TreeNode root, int deep) {
        if (root == null) return deep;
        deep++;
        return Math.max(dfs(root.left, deep), dfs(root.right, deep)); 
    }
}

方法二:dfs,递归到底后,向上回溯时比较最大深度

class Solution {
    public int maxDepth(TreeNode root) {
        if (root == null) return 0;
        return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
        // 分开写:
        //int left = maxDepth(root.left);
        //int right = maxDepth(root.right);
        //return Math.max(left, right) + 1;
    }
}

方法三:bfs,每遍历一层返回值加一

class Solution {
    public int maxDepth(TreeNode root) {
        int res = 0;
        Queue<TreeNode> queue = new ArrayDeque<>();
        if (root != null) queue.offer(root);
        while (!queue.isEmpty()) {
            int len = queue.size();
            while (len-- > 0) {
                TreeNode node = queue.poll();
                if (node.left != null) queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
            }
            res += 1;
        }
        return res;
    }
}

111. 二叉树的最小深度

Leetcode 链接
给定一个二叉树,找出其最小深度。最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。

示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:2

示例 2:
输入:root = [2,null,3,null,4,null,5,null,6]
输出:5
题解:

方法一:dfs
注意不同于最大深度,[1,2] 的最小深度为 2,而不是 1,因为结点 1 只有一个孩子结点。

class Solution {
    public int minDepth(TreeNode root) {
        if (root == null) return 0;
        // 没有左孩子向右递归,没有右孩子向左递归
        if (root.left == null) return minDepth(root.right) + 1;
        if (root.right == null) return minDepth(root.left) + 1;
        return Math.min(minDepth(root.left), minDepth(root.right)) + 1;
    }
}

方法二:bfs
第一次遇到同时没有左右孩子的结点时,该层层数就为最小深度

class Solution {
    public int minDepth(TreeNode root) {
		Deque<TreeNode> deque = new ArrayDeque<>();
		if (root != null) deque.offer(root);
		int res = 0;
		while (!deque.isEmpty()) {
			int len = deque.size();
			while (len-- > 0) {
				TreeNode cur = deque.poll();
				// 第一次遇到同时没有左右孩子的结点时
				if (cur.right == null && cur.left == null) return ++res;
				if (cur.left!=null) deque.offer(cur.left);
				if (cur.right!=null) deque.offer(cur.right);
			}
			res++;
		}
		return res;
    }
}

你可能感兴趣的:(算法,leetcode,链表,算法,二叉树,数据结构)