2049. 统计最高分的节点数目
题目描述:
给你一棵根节点为 0 的 二叉树 ,它总共有 n 个节点,节点编号为 0 到 n - 1 。同时给你一个下标从 0 开始的整数数组 parents 表示这棵树,其中 parents[i] 是节点 i 的父节点。由于节点 0 是根,所以 parents[0] == -1 。一个子树的 大小 为这个子树内节点的数目。每个节点都有一个与之关联的 分数 。求出某个节点分数的方法是,将这个节点和与它相连的边全部 删除 ,剩余部分是若干个 非空 子树,这个节点的 分数 为所有这些子树 大小的乘积 。请你返回有 最高得分 节点的 数目 。
提示:
- n == parents.length
- 2 <= n <= 105
- parents[0] == -1
- 对于 i != 0 ,有 0 <= parents[i] <= n - 1
- parents 表示一棵二叉树。
示例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可以看出,得分由三部分组成:当前节点的左子树的节点总数 * 当前节点的右子树的节点总数 * 当前节点的父节点所在子树删除该分支后的节点总数
其中,当前节点的父节点删除该分支后的节点总数 = 父节点所在子树的节点总数 - 当前节点所在子树的节点总数
特殊情况有:
- 当移除节点为根节点,得分 = 根节点左子树的节点总数 * 根节点右子树的节点总数;
- 当移除节点为叶子节点,得分 = 整棵二叉树的节点总数 - 1;
因此,我们首先要统计出各个节点所在子树的节点总数,再根据移除节点的情况计算出得分,通过比较算出最高得分以及最高得分的节点数。值得注意的是,所给定输入是一个存储着父节点索引(因为题目中节点是按0~n编号的)的数组,数组的值不是Node类型,因此我们无法用往常针对二叉树的深度优先遍历的方法来统计节点数。
算法设计流程:
1.我们可以用一个Map来存储各个节点的子节点,key为当前节点索引(即节点编号),value为存储子节点编号的数组,数组长度要么是1(只有一棵子树),要么是2(左右子树都有)。而对于叶子节点(也即没有子节点)我们不将它们存进来。使用Map可以将查找的时间复杂度降低到O(1);
2.使用递归实现深度优先遍历,统计各个节点所在子树的节点总数,自底向上,一次性统计完,避免重复计算。这里使用一个数组来存放各个节点所在子树的节点总数,数组下标对应节点编号;
3.按节点编号顺序依次移除节点,首先去Map中查找该节点,若没有,证明是叶子节点,得分为n-1;若存在,看子节点数组长度:
- 等于1,表示只有一个子树;
- 等于2,表示有两棵子树;
4.将移除了当前节点的得分与历史最高得分比较,
- 若大于历史最高得分,则将之前统计的最高得分节点数重新置一,并更新最高得分为当前得分;
- 若相等,则最高得分节点数加一
代码如下:
var countHighestScoreNodes = function(parents) { let n = parents.length; let map = new Map(); for(let i=1;i<n;i++){ let p = parents[i]; //取得节点编号为数组下标的父节点编号 let arr = []; if(map.has(p)){//如果存在该父节点,证明它已有一个子节点,arr.push()则将它的另一个子节点存进来 arr = map.get(p); } arr.push(i); map.set(p,arr); } let number = new Array(n); //用来存储节点编号为数组下标,所在子树节点总数为数组值
dps(0,map,number); //从根节点进行递归,自底向上统计
let max = 0; let ans = 0;
for(let i=0;i<n;i++){ //这里按节点编号从0~n-1的顺序进行移除 let res = 0; if(map.has(i)){ let arr = map.get(i); let other = n-number[i]>0?n-number[i]:1; //other表示:父节点所在子树的节点总数 - 当前节点所在子树的节点总数 若计算结果等于0,证明是根节点,设other=1,防止后面乘以0 if(arr.length == 1){ res = number[arr[0]]*other; //一棵子树的情况 }else if(arr.length==2){ res = number[arr[0]]*number[arr[1]]*other; //两棵子树的情况 } }else{ res = n-1; //叶子节点的情况 } if(res >max){ max = res; ans = 1; }else if(res == max){ ans++; } } return ans; }; //统计节点数量 function dps(root,map,number){ if(map.has(root)){ //若在Map中找到该节点编号,证明它不是叶子节点 let arr = map.get(root); if(arr.length == 1){ //若只有一个子节点,则总节点数 = 左子树总节点数 + 当前节点 number[root] = dps(arr[0],map,number)+1; return number[root]; }else{ //否则有两个子节点,总节点数 = 左子树总节点数 + 右子树总节点数 +当前节点 number[root] = dps(arr[0],map,number)+dps(arr[1],map,number)+1; return number[root]; } }else{ number[root] = 1; //叶子节点的节点总数为1 return 1; } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理