题目描述
思路解析动画文字版
记住这条主线:先下钻到叶子,再自底向上结算每个子树和,边算边往哈希表记一笔。
准备 · 从根开始后序遍历:开局哈希表是空的。后序 DFS 从根节点 50 出发,先一路下钻到最左下的叶子,再自底向上一个个结算子树和。盯住右边这张计数表,它会随着结算逐行长出来。
下钻 · 进入 50:递归进入节点 50(紫色)。它还有孩子,子树和现在算不了,得先把左右孩子的子树和都求出来,所以继续往下钻。
下钻 · 进入 20:递归进入节点 20(紫色)。它还有孩子,子树和现在算不了,得先把左右孩子的子树和都求出来,所以继续往下钻。
下钻 · 进入 30:递归进入节点 30(紫色)。它还有孩子,子树和现在算不了,得先把左右孩子的子树和都求出来,所以继续往下钻。
下钻 · 到叶子 10:递归走到节点 10(紫色),它没有孩子,是叶子。叶子的子树就是它自己一个人,下一帧直接结算。
结算 · 10 的子树和 10:节点 10 的左右子树和都齐了,结算:子树和 = 10。把 10 记进哈希表,它是第一次出现,计数记 1。节点变绿表示已经处理完。
下钻 · 到叶子 40:递归走到节点 40(紫色),它没有孩子,是叶子。叶子的子树就是它自己一个人,下一帧直接结算。
结算 · 40 的子树和 40:节点 40 的左右子树和都齐了,结算:子树和 = 40。把 40 记进哈希表,它是第一次出现,计数记 1。节点变绿表示已经处理完。
结算 · 30 的子树和 80:节点 30 的左右子树和都齐了,结算:子树和 = 30 + 10 + 40 = 80。把 80 记进哈希表,它是第一次出现,计数记 1。节点变绿表示已经处理完。
下钻 · 到叶子 -10:递归走到节点 -10(紫色),它没有孩子,是叶子。叶子的子树就是它自己一个人,下一帧直接结算。
结算 · -10 的子树和 -10:节点 -10 的左右子树和都齐了,结算:子树和 = (-10)。把 -10 记进哈希表,它是第一次出现,计数记 1。节点变绿表示已经处理完。
结算 · 20 的子树和 90:节点 20 的左右子树和都齐了,结算:子树和 = 20 + 80 + (-10) = 90。把 90 记进哈希表,它是第一次出现,计数记 1。节点变绿表示已经处理完。
下钻 · 进入 80:递归进入节点 80(紫色)。它还有孩子,子树和现在算不了,得先把左右孩子的子树和都求出来,所以继续往下钻。
下钻 · 到叶子 40:递归走到节点 40(紫色),它没有孩子,是叶子。叶子的子树就是它自己一个人,下一帧直接结算。
结算 · 40 的子树和 40:节点 40 的左右子树和都齐了,结算:子树和 = 40。把 40 记进哈希表,这个和之前出现过,计数涨到 2。节点变绿表示已经处理完。
下钻 · 到叶子 10:递归走到节点 10(紫色),它没有孩子,是叶子。叶子的子树就是它自己一个人,下一帧直接结算。
结算 · 10 的子树和 10:节点 10 的左右子树和都齐了,结算:子树和 = 10。把 10 记进哈希表,这个和之前出现过,计数涨到 2。节点变绿表示已经处理完。
结算 · 80 的子树和 130:节点 80 的左右子树和都齐了,结算:子树和 = 80 + 40 + 10 = 130。把 130 记进哈希表,它是第一次出现,计数记 1。节点变绿表示已经处理完。
结算 · 50 的子树和 270:节点 50 的左右子树和都齐了,结算:子树和 = 50 + 90 + 130 = 270。把 270 记进哈希表,它是第一次出现,计数记 1。节点变绿表示已经处理完。
统计完成 · 扫计数表找最多:九个节点全部结算完,计数表也满了。现在扫一遍这张表,找出现次数最大的那个。可以看到 10 和 40 都出现了 2 次,其余的和都只出现 1 次,所以最大次数是 2。
得到答案 · 并列最多的全都要:次数等于 2 的和有两个:10 和 40,它俩并列最多。题目要求把出现次数最多的和全部返回,所以答案是 [10, 40]。这也提醒我们:最多的可能不止一个,别只返回一个就交卷。
边界要想清:单节点直接返回;全部各出现一次时,所有和都是答案。
面试可追问爆栈与一次成型,思路都围绕「后序结算 + 边算边记」。
参考代码
from __future__ import annotationsfrom typing import *from collections import *from functools import *from itertools import *from math import *from heapq import *from bisect import *from string import *from operator import *class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = rightclass ListNode: def __init__(self, val=0, next=None): self.val = val self.next = nextclass Solution: def findFrequentTreeSum(self, root: Optional[TreeNode]) -> List[int]: cnt = Counter() def dfs(node): if not node: return 0 s = node.val + dfs(node.left) + dfs(node.right) cnt[s] += 1 return s dfs(root) if not cnt: return [] best = max(cnt.values()) return sorted([s for s, c in cnt.items() if c == best])复杂度
- 时间:O(n),每个节点只在后序里被访问、结算一次,哈希表的记一笔和最后扫表都是均摊 O(1)
- 空间:O(n),哈希表最多存 n 个不同的子树和;递归栈深为树高 h,最坏(链状树)也是 O(n)
易错点
面试追问把动画讲成自己的话
追问如果树非常深,递归会不会爆栈?怎么办?
追问怎么一次遍历就同时拿到最大次数和答案?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
在每个树行中找最大值
LeetCode 515 · 中等 · 沿着 树套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题