树与层次遍历青铜挑战
理解树的结构
通过中序和后序遍历序列恢复二叉树是一个经典的二叉树构建问题。给定二叉树的中序遍历序列和后序遍历序列,我们可以利用以下步骤进行恢复。
思路:
- 后序遍历的特点:
- 后序遍历的最后一个节点是树的根节点。
- 中序遍历的特点:
- 中序遍历中,根节点左边的元素是左子树,根节点右边的元素是右子树。
步骤:
- 从后序遍历的最后一个节点开始,它是树的根节点。
- 在中序遍历中找到该根节点的位置,它把中序遍历序列分成两部分:左子树和右子树。
- 递归地对左子树和右子树的中序遍历和后序遍历进行相同的操作,直到所有节点都被处理。
import java.util.HashMap;
import java.util.Map;
public class BinaryTreeBuilder {
// 定义二叉树节点结构
static class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int val) {
this.val = val;
}
}
// 用于存储中序遍历的节点索引,方便快速查找
private Map<Integer, Integer> inorderMap;
// 主方法,传入中序和后序序列,建立二叉树
public TreeNode buildTree(int[] inorder, int[] postorder) {
inorderMap = new HashMap<>();
// 将中序遍历的节点及其对应索引存入map
for(int i = 0; i < inorder.length; i++){
inorderMap.put(inorder[i], i);
}
// 从后序数组的最后一个元素开始,递归构建树
return build(inorder, postorder, 0, inorder.length - 1, new int[]{postorder.length - 1});
}
// 递归构建树
private TreeNode build(int[] inorder, int[] postorder, int inStart, int inEnd, int[] postIndex){
// 递归终止条件:没有元素可处理
if (inStart > inEnd) {
return null;
}
// 确保postIndex不会越界
if (postIndex[0] < 0) {
return null;
}
//后序遍历的当前节点
int rootVal = postorder[postIndex[0]--];
TreeNode root = new TreeNode(rootVal);
// 获取当前根节点在中序遍历中的位置
int rootIndex = inorderMap.get(rootVal);
// 先构建右子树,再构建左子树(因为postorder是后序遍历)
root.right = build(inorder, postorder, rootIndex + 1, inEnd, postIndex);
root.left = build(inorder, postorder, inStart, rootIndex - 1, postIndex);
return root;
}
// 辅助方法:打印二叉树(中序遍历)
public void inorderTraversal(TreeNode root){
if(root != null){
inorderTraversal(root.left);
System.out.print(root.val + " ");
inorderTraversal(root.right);
}
}
public static void main(String[] args) {
BinaryTreeBuilder builder = new BinaryTreeBuilder();
//示例
int[] inorder = {9, 3, 15, 20, 7};
int[] postorder = {9, 15, 7, 20, 3};
// 构建二叉树
TreeNode root = builder.buildTree(inorder, postorder);
//打印构建的二叉树的中序遍历作验证
System.out.println("Inorder Traversal: ");
builder.inorderTraversal(root);
}
}
-
后序遍历:后序遍历的最后一个元素是
3
,所以树的根节点是3
。 -
中序遍历:在中序遍历数组中,
3
位于索引位置1
,这意味着我们可以将树分为左右子树:- 左子树:中序序列
{9}
(在3
左边) - 右子树:中序序列
{15, 20, 7}
(在3
右边)
- 左子树:中序序列
-
递归构建左子树和右子树:
- 左子树:对应的后序序列是
{9}
。所以左子树的根节点就是9
,没有子节点(因为没有更多元素)。 - 右子树:右子树的中序序列是
{15, 20, 7}
,后序序列是{15, 7, 20}
。我们从后序序列中取出20
作为右子树的根节点。然后将其继续分为左右子树:- 左子树:中序序列
{15}
和后序序列{15}
,所以左子树的根节点就是15
。 - 右子树:中序序列
{7}
和后序序列{7}
,所以右子树的根节点就是7
。
- 左子树:中序序列
- 左子树:对应的后序序列是
构建出的树结构:
3
/ \
9 20
/ \
15 7
树与层次遍历白银挑战
二叉树层次遍历的经典问题
二叉树层次遍历
二叉树的层次遍历(也叫广度优先遍历,BFS)是指从根节点开始,逐层遍历树的节点,每一层从左到右依次访问。
在 Java 中实现二叉树的层次遍历可以使用队列(Queue)来辅助。队列是一种先进先出的数据结构,适合用来逐层遍历树的节点。
假设你已经有了二叉树的定义(TreeNode
类),可以按以下方式实现层次遍历:
import java.util.LinkedList;
import java.util.Queue;
class TreeNode {
int val;
TreeNode left, right;
TreeNode(int val) {
this.val = val;
left = right = null;
}
}
public class BinaryTreeLevelOrderTraversal {
public void levelOrder(TreeNode root) {
if (root == null) {
return;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root); // 将根节点放入队列
while (!queue.isEmpty()) {
TreeNode currentNode = queue.poll(); // 取出队首元素
// 访问当前节点
System.out.println(currentNode.val + " ");
// 如果左子节点不为空,将其加入队列
if (currentNode.left != null) {
queue.offer(currentNode.left);
}
// 如果右子节点不为空,将其加入队列
if (currentNode.right != null) {
queue.offer(currentNode.right);
}
}
}
public static void main(String[] args) {
BinaryTreeLevelOrderTraversal tree = new BinaryTreeLevelOrderTraversal();
// 创建一个示例二叉树
TreeNode root = new TreeNode(1);
root.left = new TreeNode(2);
root.right = new TreeNode(3);
root.left.left = new TreeNode(4);
root.left.right = new TreeNode(5);
root.right.left = new TreeNode(6);
root.right.right = new TreeNode(7);
// 执行层次遍历
System.out.print("Level Order Traversal: ");
tree.levelOrder(root);
}
}
- TreeNode 类:代表二叉树的节点,每个节点有一个整数值(
val
),以及指向左右子节点的指针(left
和right
)。 - levelOrder 函数:这个方法实现了层次遍历。它使用一个队列来逐层遍历树的节点。根节点首先入队,然后每次从队列中取出一个节点,访问它的值,并将它的左右子节点加入队列,直到队列为空。
- 主函数(main):创建一个示例二叉树并调用
levelOrder
函数进行层次遍历。
- 时间复杂度:O(n),其中 n 是树中的节点数。每个节点都只会被访问一次。
- 空间复杂度:O(n),最坏情况下,队列中最多会保存树的最大宽度的节点数(即叶子节点的数目)。
处理不同层的值
例题一 在每个树行中找最大值
给定一棵二叉树的根节点 root
,请找出该二叉树中每一层的最大值。
leetcode515
你可以通过广度优先搜索 (BFS) 来遍历二叉树的每一层,然后找出每一层的最大值。具体做法是使用一个队列存储每一层的节点,遍历完一层后找出该层的最大值。
下面是使用 Java 实现的代码:
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
public class LargestValues {
public List<Integer> largestValues(TreeNode root){
List<Integer> result = new ArrayList<>();
if (root == null) {
return result;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int levelSize = queue.size();
// 初始化当前层最大值为最小整数
int maxVal = Integer.MIN_VALUE;
for(int i = 0; i < levelSize; i++) {
TreeNode currentNode = queue.poll();
// 更新当前层最大值
maxVal = Math.max(maxVal, currentNode.val);
if (currentNode.left != null) {
queue.offer(currentNode.left);
}
if (currentNode.right != null) {
queue.offer(currentNode.right);
}
}
result.add(maxVal);
}
return result;
}
public static void main(String[] args) {
TreeNode root = new TreeNode(1);
root.left = new TreeNode(3);
root.right = new TreeNode(2);
root.left.left = new TreeNode(5);
root.left.right = new TreeNode(3);
root.right.right = new TreeNode(9);
LargestValues solution = new LargestValues();
List<Integer> result = solution.largestValues(root);
System.out.println(result); // Output: [1, 3, 9]
}
}
时间复杂度:
- O(n),其中 n 是二叉树的节点数。每个节点都被访问一次。
空间复杂度:
- O(m),其中 m 是二叉树的最大宽度。最坏情况下,队列存储的是树的最大宽度的节点。
例题二 二叉树的层平均值
给定一个非空二叉树的根节点 root
, 以数组的形式返回每一层节点的平均值。与实际答案相差 10-5
以内的答案可以被接受。
leetcode637
解法:
同样可以使用层序遍历(BFS)来解这个问题,逐层计算每一层的节点平均值。我们可以使用一个队列来帮助实现层序遍历。对于每一层,计算所有节点的和,并除以该层节点的个数,最后返回结果。
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
public class AverageOfLevels {
public List<Double> averageOfLevels(TreeNode root){
List<Double> result = new ArrayList<>();
if (root == null) {
return result;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int levelSize = queue.size();
double levelSum = 0;
for(int i = 0; i < levelSize; i++){
TreeNode node = queue.poll();
levelSum += node.val;
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
result.add(levelSum / levelSize);
}
return result;
}
}
例题三 二叉树的右视图
给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
leetcode199
-
BFS遍历:我们使用一个队列
queue
来进行广度优先遍历。每次取出一层的节点,然后依次访问这一层的所有节点。 -
每层最右节点:在遍历一层时,我们只关注该层的最右边的节点,因此在每次循环结束时,我们记录当前层的最后一个节点的值(即右侧可见的节点)。
-
更新队列:每访问一个节点时,将它的左右子节点(如果有的话)加入队列,这样可以确保后续的层次遍历。
-
返回结果:最终,
result
列表中存储的就是每一层的最右边的节点值,这就是从右侧所能看到的节点值。
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
public class RightSideView {
public List<Integer> rightSideView(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null) {
return result;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
TreeNode rightMostNode = null;
for(int i = 0; i < size; i++){
TreeNode currentNode = queue.poll();
rightMostNode = currentNode;
if (currentNode.left != null) {
queue.offer(currentNode.left);
}
if (currentNode.right != null) {
queue.offer(currentNode.right);
}
}
result.add(rightMostNode.val);
}
return result;
}
}
- 时间复杂度是 O(n),其中
n
是二叉树中的节点数。我们需要遍历每一个节点。 - 空间复杂度是 O(m),其中
m
是二叉树的最大层宽度(即队列中最多存储的节点数)。
例题四 找树左下角的值
给定一个二叉树的 根节点 root
,请找出该二叉树的 最底层 最左边 节点的值。
假设二叉树中至少有一个节点。
leetcode513
解法:
- 层序遍历:队列中保存着当前正在遍历的节点。对于每一层的节点,我们需要记录该层的第一个节点,这就是我们所需的“最左边的节点”。
leftmostValue
:记录每层第一个节点的值。每次进入新的一层,i == 0
时会更新该变量为当前层的第一个节点的值。- 队列中的操作:从左到右遍历当前层的节点,每次加入左子节点和右子节点到队列中。
import java.util.LinkedList;
import java.util.Queue;
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
public class FindBottomLeftValue {
public int findBottomLeftValue(TreeNode root){
if (root == null) {
return -1;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
int leftmostValue = 0;
while (!queue.isEmpty()) {
int levelSize = queue.size();
for (int i = 0; i < levelSize; i++) {
TreeNode currentNode = queue.poll();
if (i == 0) {
leftmostValue = currentNode.val;
}
if (currentNode.left != null) {
queue.offer(currentNode.left);
}
if (currentNode.right != null) {
queue.offer(currentNode.right);
}
}
}
return leftmostValue;
}
}
- 时间复杂度仍然是 O(N),因为每个节点都会被访问一次。
- 空间复杂度是 O(N),因为在最坏的情况下,队列需要存储二叉树的最后一层节点。