leetcode
leetcode 1801 ~ 1850
统计最高分的节点数目

统计最高分的节点数目

难度:

标签:

题目描述

给你一棵根节点为 0 的 二叉树 ,它总共有 n 个节点,节点编号为 0 到 n - 1 。同时给你一个下标从 0 开始的整数数组 parents 表示这棵树,其中 parents[i] 是节点 i 的父节点。由于节点 0 是根,所以 parents[0] == -1 。

一个子树的 大小 为这个子树内节点的数目。每个节点都有一个与之关联的 分数 。求出某个节点分数的方法是,将这个节点和与它相连的边全部 删除 ,剩余部分是若干个 非空 子树,这个节点的 分数 为所有这些子树 大小的乘积 。

请你返回有 最高得分 节点的 数目 。

 

示例 1:

example-1

输入:parents = [-1,2,0,2,0]
输出:3
解释:
- 节点 0 的分数为:3 * 1 = 3
- 节点 1 的分数为:4 = 4
- 节点 2 的分数为:1 * 1 * 2 = 2
- 节点 3 的分数为:4 = 4
- 节点 4 的分数为:4 = 4
最高得分为 4 ,有三个节点得分为 4 (分别是节点 1,3 和 4 )。

示例 2:

example-2

输入:parents = [-1,2,0]
输出:2
解释:
- 节点 0 的分数为:2 = 2
- 节点 1 的分数为:2 = 2
- 节点 2 的分数为:1 * 1 = 1
最高分数为 2 ,有两个节点分数为 2 (分别为节点 0 和 1 )。

 

提示:

  • n == parents.length
  • 2 <= n <= 105
  • parents[0] == -1
  • 对于 i != 0 ,有 0 <= parents[i] <= n - 1
  • parents 表示一棵二叉树。

代码结果

运行时间: 308 ms, 内存: 47.0 MB


// 思路:
// 1. 构建孩子列表。
// 2. 使用递归计算每个节点的子树大小。
// 3. 计算每个节点的分数并统计最高分数的节点数。
// 4. 使用 Java Stream API 进行流处理。

import java.util.*;
import java.util.stream.*;

public class TreeHighestScoreStream {
    public int countHighestScoreNodes(int[] parents) {
        int n = parents.length;
        List<List<Integer>> children = IntStream.range(0, n)
                .mapToObj(i -> new ArrayList<Integer>())
                .collect(Collectors.toList());
        for (int i = 1; i < n; i++) {
            children.get(parents[i]).add(i);
        }

        int[] subtreeSize = new int[n];
        dfs(0, children, subtreeSize);

        int[] scores = new int[n];
        IntStream.range(0, n).forEach(i -> {
            int score = 1;
            int totalSize = n - 1;
            for (int child : children.get(i)) {
                score *= subtreeSize[child];
                totalSize -= subtreeSize[child];
            }
            if (i != 0) {
                score *= totalSize;
            }
            scores[i] = score;
        });

        int maxScore = Arrays.stream(scores).max().orElse(0);
        return (int) Arrays.stream(scores).filter(score -> score == maxScore).count();
    }

    private int dfs(int node, List<List<Integer>> children, int[] subtreeSize) {
        int size = 1;
        for (int child : children.get(node)) {
            size += dfs(child, children, subtreeSize);
        }
        subtreeSize[node] = size;
        return size;
    }
}

解释

方法:

该题解采用深度优先搜索(DFS)的方法来解决问题。首先,通过父节点数组构建一个树的子节点列表表示。对每个节点,使用DFS来计算以该节点为根的子树的大小,并计算删除该节点后其他部分的大小乘积作为该节点的得分。在DFS过程中,同时更新最高得分和得分最高的节点数。整体过程避免了重复计算,每个节点只被访问一次,确保了效率。

时间复杂度:

O(n)

空间复杂度:

O(n)

代码细节讲解

🦆
在构建树的子节点列表时,你是如何确保每个节点都正确地被分配到其父节点的子列表中的?
在构建子节点列表的过程中,通过遍历`parents`数组来完成。对于数组中的每个元素(除了根节点,其父节点索引为-1),我会将当前节点索引(即遍历的索引)添加到父节点的子列表中。这里使用`self.children[p].append(i)`实现,其中`p`是当前节点的父节点索引,`i`是当前节点索引。由于是直接根据`parents`数组进行操作,每个节点都会被正确地分配到其父节点的子列表中。
🦆
你的DFS函数是如何确保每个节点只被访问一次的,并且它是怎样避免重复计算的?
DFS函数通过递归的方式访问每个节点。对于每个节点,它首先计算其所有子节点的大小,并在此过程中累计分数。由于每个节点在DFS中只调用一次`dfs(c)`(其中`c`是子节点),这保证了每个节点只被访问一次。关于避免重复计算,每个节点的子树大小在首次计算后通过返回值传递给父节点,而不需要重新计算,这样有效避免了重复工作。
🦆
为什么在计算节点分数时要初始化分数为1,并在遍历子节点时累乘子树大小?这种方法是否可能导致分数的溢出?
初始化分数为1是因为分数的计算依赖于乘积操作,且乘法的单位元是1。在遍历子节点时,每个子树大小与当前分数相乘,这样可以得到分离该节点后其他部分的大小乘积,作为该节点的得分。这种方法确实可能导致分数溢出,尤其是在处理大数据集或树深度较大时。在实际应用中,可能需要使用更大范围的变量类型或在某些语言中处理大整数操作以防止溢出。
🦆
当节点为根节点时,你是如何处理其分数计算的?为什么根节点的处理逻辑与其他节点不同?
在根节点的分数计算中,不需要包括其父节点子树的大小,因为根节点没有父节点。因此,对于根节点,其得分仅取决于其所有子树的大小的乘积。这与其他节点不同,因为非根节点需要计算包括其父节点中其余部分的大小。根节点的特殊处理是因为它在树结构中具有唯一性,作为树的最顶层节点。

相关问题