关注Java领域相关技术 记录有趣的事情

LeetCode-637. 二叉树的层平均值

US-B.Ralph
US-B.Ralph
2020-09-13

问题地址

LeetCode每日一题/2020-09-12

LeetCode637. 二叉树的层平均值


问题描述

规则

  • 给定一个非空二叉树, 返回一个由每层节点平均值组成的数组。

示例

  • 示例一:
输入:
    3
   / \
  9  20
    /  \
   15   7
输出:[3, 14.5, 11]
解释:
第 0 层的平均值是 3 ,  第1层是 14.5 , 第2层是 11 。因此返回 [3, 14.5, 11] 。

提示

  • 节点值的范围在32位有符号整数范围内。

解析

解题思路

  • 计算每一层的平均值,需要在搜索中存储每一层数据的和、每一层元素的个数;
  • 遍历结束后每一层的平均值=每一层数据的和/每一层元素的个数;

数据操作分析

  • 计算每一层的平均值,需要在搜索中存储每一层数据的和、每一层元素的个数;
  • 遍历结束后每一层的平均值=每一层数据的和/每一层元素的个数;

复杂度分析

  1. 时间复杂度O(n),其中 n 是二叉树中的节点个数。

  2. 空间复杂度O(n),其中 n 是二叉树中的节点个数。

编码实现

public class LeetCode0637_AverageOfLevelsInBinaryTtree {
    public List<Double> averageOfLevels(TreeNode root) {
        //放每一层的数字
        Map<Integer, List<Integer>> map = new HashMap<>();
        dfs(root, 1, map);
        int layerNum = map.keySet().size();
        List<Double> res = new ArrayList<>(layerNum*2);
        for (int i = 0; i < layerNum; i++) {
            List<Integer> list = map.get(i + 1);
            double sum = 0.0;
            for (Integer num : list
            ) {
                sum += num;
            }
            res.add(sum / list.size());
        }
        return res;
    }

    private void dfs(TreeNode root, int curLayerNum, Map<Integer, List<Integer>> map) {
        if (root == null) return;
        cale(curLayerNum, map, root);

        if (root.left == null && root.right == null) {
            return;
        }
        curLayerNum++;
        if (root.left != null) dfs(root.left, curLayerNum, map);
        if (root.right != null) dfs(root.right, curLayerNum, map);

    }

    private void cale(int curLayerNum, Map<Integer, List<Integer>> map, TreeNode root) {
        List<Integer> tmp;
        if (map.get(curLayerNum) == null) {
            tmp = new ArrayList<>();
        } else {
            tmp = map.get(curLayerNum);
        }
        tmp.add(root.val);
        map.put(curLayerNum, tmp);
    }

官方解法

方法一:深度优先搜索

思路:

  • 使用深度优先搜索计算二叉树的层平均值,需要维护两个数组,\textit{counts} 用于存储二叉树的每一层的节点数,\textit{sums} 用于存储二叉树的每一层的节点值之和。搜索过程中需要记录当前节点所在层,如果访问到的节点在第 i 层,则将 \textit{counts}[i] 的值加 1,并将该节点的值加到 \textit{sums}[i]
  • 遍历结束之后,第 i 层的平均值即为 \textit{sums}[i] / \textit{counts}[i]

复杂度分析

  1. 时间复杂度 O(n),其中 n 是二叉树中的节点个数。
  • 深度优先搜索需要对每个节点访问一次,对于每个节点,维护两个数组的时间复杂度都是 O(1),因此深度优先搜索的时间复杂度是 O(n)
  • 遍历结束之后计算每层的平均值的时间复杂度是 O(h),其中 h 是二叉树的高度,任何情况下都满足 h \le n
  • 因此总时间复杂度是 O(n)
  1. 空间复杂度:O(n),其中 n 是二叉树中的节点个数。空间复杂度取决于两个数组的大小和递归调用的层数,两个数组的大小都等于二叉树的高度,递归调用的层数不会超过二叉树的高度,最坏情况下,二叉树的高度等于节点个数。

编码实现

class Solution {
    public List<Double> averageOfLevels(TreeNode root) {
        List<Integer> counts = new ArrayList<Integer>();
        List<Double> sums = new ArrayList<Double>();
        dfs(root, 0, counts, sums);
        List<Double> averages = new ArrayList<Double>();
        int size = sums.size();
        for (int i = 0; i < size; i++) {
            averages.add(sums.get(i) / counts.get(i));
        }
        return averages;
    }

    public void dfs(TreeNode root, int level, List<Integer> counts, List<Double> sums) {
        if (root == null) {
            return;
        }
        if (level < sums.size()) {
            sums.set(level, sums.get(level) + root.val);
            counts.set(level, counts.get(level) + 1);
        } else {
            sums.add(1.0 * root.val);
            counts.add(1);
        }
        dfs(root.left, level + 1, counts, sums);
        dfs(root.right, level + 1, counts, sums);
    }
}

方法二:广度优先搜索

思路:

  • 也可以使用广度优先搜索计算二叉树的层平均值。从根节点开始搜索,每一轮遍历同一层的全部节点,计算该层的节点数以及该层的节点值之和,然后计算该层的平均值。
  • 如何确保每一轮遍历的是同一层的全部节点呢?我们可以借鉴层次遍历的做法,广度优先搜索使用队列存储待访问节点,只要确保在每一轮遍历时,队列中的节点是同一层的全部节点即可。具体做法如下:
    • 初始时,将根节点加入队列;
    • 每一轮遍历时,将队列中的节点全部取出,计算这些节点的数量以及它们的节点值之和,并计算这些节点的平均值,然后将这些节点的全部非空子节点加入队列,重复上述操作直到队列为空,遍历结束。
  • 由于初始时队列中只有根节点,满足队列中的节点是同一层的全部节点,每一轮遍历时都会将队列中的当前层节点全部取出,并将下一层的全部节点加入队列,因此可以确保每一轮遍历的是同一层的全部节点。
  • 具体实现方面,可以在每一轮遍历之前获得队列中的节点数量 \textit{size}size,遍历时只遍历 \textit{size}size 个节点,即可满足每一轮遍历的是同一层的全部节点。

复杂度分析

  1. 时间复杂度 O(n),其中 n 是二叉树中的节点个数。
  • 广度优先搜索需要对每个节点访问一次,时间复杂度是 O(n)
  • 需要对二叉树的每一层计算平均值,时间复杂度是 O(h),其中 h 是二叉树的高度,任何情况下都满足 h \le n
  • 因此总时间复杂度是 O(n)
  1. 空间复杂度:O(n),其中 n 是二叉树中的节点个数。空间复杂度取决于队列开销,队列中的节点个数不会超过 n

编码实现

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

精彩评论

跳转地址1:637. 二叉树的层平均值:【二叉树层序遍历】详解!

思路

  • 这道题目就是考察二叉树的层序遍历。
  • 二叉树的层序遍历还有这两道题目,大家可以先做一下,其实都是一个思路
  • 想要学习二叉树的深度遍历可以看这里彻底吃透前中后序递归法。
  • 而本题呢,就是把每一层的结果求一个和,然后取平均值。
  • 层序遍历一个二叉树,需要借用一个辅助数据结构队列来实现,队列先进先出,符合一层一层遍历的逻辑,而是用栈先进后出适合模拟深度优先遍历也就是递归的逻辑
  • 使用队列实现广度优先遍历,动画如下:

这样就实现了层序从左到右遍历二叉树。

编码实现

class Solution {
public:
    vector<double> averageOfLevels(TreeNode* root) {
        queue<TreeNode*> que;
        if (root != NULL) que.push(root);
        vector<double> result;
        while (!que.empty()) {
            int size = que.size();
            double sum = 0; // 统计每一层的和
            for (int i = 0; i < size; i++) { // 这里一定要使用固定大小size,不要使用que.size()
                TreeNode* node = que.front();
                que.pop();
                sum += node->val;
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
            }
            result.push_back(sum / size); // 将每一层放进结果集
        }
        return result;
    }
};
US-B.Ralph
LeetCode数据结构与算法算法

Leave a Comment

邮箱地址不会被公开。 必填项已用*标注

17 − 1 =