一、题目
给你一个二叉树的根节点 root
。设根节点位于二叉树的第 1
层,而根节点的子节点位于第 2
层,依此类推。
请返回层内元素之和 最大 的那几层(可能只有一层)的层号,并返回其中 最小 的那个。
二、示例
2.1> 示例 1:
【输入】root = [1,7,0,7,-8,null,null]
【输出】2
【解释】 第 1 层各元素之和为 1, 第 2 层各元素之和为 7 + 0 = 7, 第 3 层各元素之和为 7 + -8 = -1, 所以我们返回第 2 层的层号,它的层内元素之和最大。
2.2> 示例 2:
【输入】root = [989,null,10250,98693,-89388,null,null,null,-32127]
【输出】2
提示:
- 树中的节点数在
[1, 104]
范围内-10^5
<= Node.val <=10^5
三、解题思路
因为题目中的要求是统计每层的总数,并返回最大总和的层级。那么既然涉及到层级,第一反应就是通过广度优先算法来逐层遍历。而如果要实现逐层遍历,我们可以借助队列的特性——先进先出来实现。
但是广度优先算法虽然会逐层的对节点进行遍历,但是,如何才能加入“层级”的概念呢?即,某个节点是第几层的。如果解决了层级的问题,这道题的解题思路自然就完整了。
3.1> 广度优先算法+虚拟头节点
为了解决层级问题,最初的一个想法就是——增加虚拟节点。即,首先将根节点标注为firstNodeOfLevel
节点,那么它的作用就是用来标注某层的第一个节点,也就是某一层的头节点。然后从根节点开始,如果获得它的左子节点有值,那么就将firstNodeOfLevel
赋值为左子节点,即:下一层的头节点。随着广度遍历的进行,当发现从队列中获得的节点等于firstNodeOfLevel节点,也就说明,遍历到了某一层的头节点了,那么再获取头节点的左子树节点,赋值为firstNodeOfLevel,并以此类推。但是,如果某个头节点的左子树并不存在,那么我们创建一个虚拟的头节点,val=0,左右子树都为null。每当发现当前从队列中获取的节点就是firstNodeOfLevel节点的是,我们就对上一层遍历到的节点进行最大值判断,如果是当前最大值,则作为预备结果进行保存。
具体操作如下所示,首先将根节点放入到队列中,此处使用的是ArrayDeque
实例对象。那么根节点Node(1)也就是firstNodeOfLevel节点。如果队列不为空,则进入while循环中,只有当队列为空,则结束while循环。在while循环中,从队列中获取头节点Node(1),由于Node(1)是firstNodeOfLevel节点,所以,获取Node(1)的左子节点Node(7)作为“全新的”firstNodeOfLevel节点,并随之把左右子树节点都放入到队列中,并且要统计该层的总和,由于当前是第一层,所以每层总和最大值暂时就是第1层了。具体如下图所示:
此时的firstNodeOfLevel是第二层的头节点了,依然通过获取其左子树来为firstNodeOfLevel赋新值,并且将Node(7)和Node(0)各自的左右节点都放入到队列中。最后要统计该层的总和,由于当前总数是7,比第一层总数1要大,所以每层总和最大值暂时就是第2层了。具体如下图所示:
当我们遍历到第三层的是,其实是最后一层了,前面我们曾经说过,当获取firstNodeOfLevel的左子节点为null的时候,我们要创建虚拟节点,即:val=0且左右子节点都为null的节点。但是,这里有个特殊的处理,就是,如果发现队列中只有firstNodeOfLevel这一个节点,那么其实说明了firstNodeOfLevel节点所在的这层也只有它自己一个节点了,所以也没必要再创建虚拟节点了,那么就当计算完第3层的总数值之后,直接结束while循环了。其中由于第3层的总数为-1,小于第2层的总数7,所以,最终每层总数最大的层数就是2了。这也就是整个方法的最终结果。具体如下图所示:
具体实现请参照4.1> 广度优先算法+虚拟头节点
3.2> 广度优先算法+层级总数统计
在3.1中,我们发现,其实这个所谓的虚拟节点的作用,就是为了标志某个节点是某层的头节点,从而方便我们去统计这层的总数。但是,这种做法其实比较麻烦,而且会导致代码量很大。其实有一个更好的办法,就是通过队列中的size。从3.1图例中我们可以发现,将某一层里的所有节点都放到Queue队列中,然后通过queue.size()
来确定当前队列中有多少个节点是这个层的,然后通过这些节点去做两件事就可以了:
事件一:获取节点的val值,并进行相加统计。
事件二:将该层中每个节点的leftNode和rightNode都放入到队列中。因为在此之前,我们已经确定了队列中有多少个节点是这层的,所以此时往队列中再放入节点的时候,是不会对该层循环统计造成什么影响的。
由于该方法只是不需要再创建firstNodeOfLevel节点指针了,而其他操作与3.1一致,所以此处就不再通过画图解释了。
那么由于我们只是通过对某层的queue.zise()来确定层级中节点的个数,免去了创建和控制firstNodeOfLevel节点的逻辑代码,所以这个实现无论是代码量还是执行效率都要比3.1高了不少。所以,推荐大家采用这种方法。
具体实现请参照4.2> 广度优先算法+层级总数统计
四、代码实现
4.1> 广度优先算法+虚拟头节点
public int maxLevelSum(TreeNode root) {
if (root == null) return 0;
int level = 1, sumNum = root.val, maxNum = sumNum, result = level;
TreeNode firstNodeOfLevel = root.left != null ? root.left : new TreeNode(0);
Queue<TreeNode> queue = new ArrayDeque();
queue.add(firstNodeOfLevel);
if (root.right != null) queue.add(root.right);
while(!queue.isEmpty()) {
TreeNode node = queue.poll();
if (node == firstNodeOfLevel) {
if (maxNum < sumNum) {
maxNum = sumNum;
result = level;
}
if (node.left != null || node.right != null || !queue.isEmpty()) {
TreeNode leftNode = node.left != null ? node.left : new TreeNode(0);
queue.add(leftNode);
firstNodeOfLevel = leftNode;
sumNum = 0;
level++;
}
} else if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
sumNum += node.val;
}
if (maxNum < sumNum) {
result = level;
}
return result;
}
4.2> 广度优先算法+层级总数统计
public int maxLevelSum(TreeNode root) {
Deque<TreeNode> queue = new ArrayDeque();
int max = Integer.MIN_VALUE, level = 1, maxSum = max, result = level;
queue.add(root);
while (!queue.isEmpty()) {
int queueSize = queue.size(), levelSum = 0;
while (queueSize > 0) {
TreeNode node = queue.poll();
if (node.left != null) queue.add(node.left);
if (node.right != null) queue.add(node.right);
levelSum += node.val;
queueSize--;
}
if (maxSum < levelSum) {
maxSum = levelSum;
result = level;
}
level++;
}
return result;
}
今天的文章内容就这些了:
写作不易,笔者几个小时甚至数天完成的一篇文章,只愿换来您几秒钟的 点赞 & 分享 。
更多技术干货,欢迎大家关注公众号“爪哇缪斯” ~ \(^o^)/ ~ 「干货分享,每天更新」