2049. 统计最高分的节点数目

LeetCode题解:删除节点后的最高得分节点数目


题目描述

给你一棵根节点为 0 的二叉树,总共有 n 个节点,节点编号为 0n - 1。同时给你一个长度为 n 的数组 parents,其中 parents[i] 表示节点 i 的父节点。由于节点 0 是根节点,故 parents[0] == -1

我们定义节点的分数如下:

  • 删除某个节点 i,即将该节点及与它相连的所有边都删除,
  • 剩余的节点构成若干个非空的子树,
  • 该节点的分数是这些子树大小的乘积。

你的任务是:

  • 找出得分最高的节点个数。

题目示例

示例 1:

输入:parents = [-1, 2, 0, 2, 0]
输出:3

解释:
- 节点 0 删除后,剩余子树大小分别是 3 和 1,分数 = 3 * 1 = 3
- 节点 1 删除后,剩余子树大小是 4,分数 = 4
- 节点 2 删除后,剩余子树大小分别是 1、1 和 2,分数 = 1 * 1 * 2 = 2
- 节点 3 删除后,剩余子树大小是 4,分数 = 4
- 节点 4 删除后,剩余子树大小是 4,分数 = 4

最高分数是 4,节点 1、3、4 得分为 4,数量为 3。

示例 2:

输入:parents = [-1, 2, 0]
输出:2

解释:
- 节点 0 删除后,剩余子树大小是 2,分数 = 2
- 节点 1 删除后,剩余子树大小是 2,分数 = 2
- 节点 2 删除后,剩余子树大小分别是 1 和 1,分数 = 1 * 1 = 1

最高分数是 2,节点 0 和 1 得分为 2,数量为 2。

解题分析

1. 理解分数的计算方式

删除节点 i 后,树被拆成了若干个子树:

  • 节点 i 的左右子树(如果有),
  • 剩余的“父树”部分,即去除该节点及其子树后剩余的其他节点组成的部分。

节点的分数是这些子树大小的乘积。


2. 题目给出的输入特点

  • parents 数组长度为 n,表示节点的父节点。
  • 节点编号从 0n-1,其中 parents[0] == -1 表示根节点。
  • 题目保证输入形成一棵有效的二叉树。

3. 需要计算的核心信息

  • 每个节点的子树大小。
  • 删除某节点后各子树的大小,用以计算分数。

解题方法

构建树结构

parents 数组中,我们能很方便地构建每个节点的子节点列表:

children = defaultdict(list)
for i in range(1, n):
    children[parents[i]].append(i)

这样,children[i] 就是节点 i 的子节点列表。


计算每个节点的子树大小

使用 DFS 遍历树,计算以每个节点为根的子树大小:

def dfs(node):
    size = 1
    for child in children[node]:
        size += dfs(child)
    subtree_size[node] = size
    return size

调用 dfs(0) 计算根节点的子树大小,进而计算所有节点的子树大小。


计算每个节点的分数

删除节点 i 后,树被拆分为以下几部分:

  • 节点 i 的所有子树,每个子树大小已知,
  • 剩余树的大小(即除去该节点及其子树后剩余节点数)

计算时:

  • 初始化分数为 1,
  • 依次乘以每个子树的大小,
  • 如果节点 i 不是根节点,那么剩余的“父树”部分大小为 n - subtree_size[i],也乘入分数。

示例代码:

score = 1
total = n - 1  # 除去当前节点本身

for child in children[i]:
    score *= subtree_size[child]
    total -= subtree_size[child]

if i != 0:  # 根节点没有“父树”部分
    score *= total

统计最高分数及节点数量

遍历所有节点,记录最大分数并统计达到该分数的节点数量。


代码实现

from collections import defaultdict
from typing import List

class Solution:
    def countHighestScoreNodes(self, parents: List[int]) -> int:
        n = len(parents)
        children = defaultdict(list)
        
        for i in range(1, n):
            children[parents[i]].append(i)
            
        subtree_size = [0] * n
        
        def dfs(node):
            size = 1
            for child in children[node]:
                size += dfs(child)
            subtree_size[node] = size
            return size
        
        dfs(0)
        
        max_score = 0
        count = 0
        
        for i in range(n):
            score = 1
            total = n - 1
            
            for child in children[i]:
                score *= subtree_size[child]
                total -= subtree_size[child]
            
            if i != 0:
                score *= total
            
            if score > max_score:
                max_score = score
                count = 1
            elif score == max_score:
                count += 1
        
        return count

复杂度分析

  • 构建子节点列表的时间复杂度:O(n)
  • DFS计算子树大小遍历所有节点一次,时间复杂度:O(n)
  • 计算所有节点分数遍历一次,时间复杂度:O(n)
  • 总时间复杂度为:O(n),空间复杂度为 O(n) 用于存储树结构及子树大小。

示例讲解

以示例 parents = [-1, 2, 0, 2, 0] 为例:

树结构如下:

0
     / \
    4   2
       / \
      1   3
  • 节点 0 的子树大小为 5(整棵树),
  • 删除节点 0,剩余子树是节点 4(大小1)和以 2 为根的子树(大小3),分数为 3 * 1 = 3。
  • 删除节点 1,剩余树大小为 4,分数为 4。
  • 删除节点 2,剩余子树分别是节点 1(大小1)、节点 3(大小1)、节点 0 和 4 组成的子树(大小2),分数为 1 * 1 * 2 = 2。
  • 删除节点 3,剩余树大小为 4,分数为 4。
  • 删除节点 4,剩余树大小为 4,分数为 4。

最高分数为 4,有三个节点达到该分数。


总结

这道题的关键在于:

  • 明确分数定义和树结构分割方式,
  • 用 DFS 计算子树大小,
  • 理解删除节点后,分成若干子树的大小计算方法,
  • 合理利用数组和字典构造树及辅助存储。

这种基于树的分治思路非常经典,掌握这类技巧对后续解决类似树结构的问题大有裨益。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值