LeetCode 508中等树 · BST
出现次数最多的子树元素和 图解题解
这道题到底在问什么
对每个节点,算出「以它为根的子树里所有节点值之和」,统计每个和出现了多少次,返回出现次数最多的那些和(可能不止一个,顺序不限)。
- 输入
- root=[5,2,-3]
- 输出
- [2,-3,4](三个和各出现 1 次,全是最多)
最优解:一步一步想明白
- 3记住这条主线:先下钻到叶子,再自底向上结算每个子树和,边算边往哈希表记一笔。
- 4栈空,哈希表空开局哈希表是空的。后序 DFS 从根节点 50 出发,先一路下钻到最左下的叶子,再自底向上一个个结算子树和。盯住右边这张计数表,它会随着结算逐行长出来。
- 5进入 50递归进入节点 50(紫色)。它还有孩子,子树和现在算不了,得先把左右孩子的子树和都求出来,所以继续往下钻。
- 6进入 20递归进入节点 20(紫色)。它还有孩子,子树和现在算不了,得先把左右孩子的子树和都求出来,所以继续往下钻。
- 7进入 30递归进入节点 30(紫色)。它还有孩子,子树和现在算不了,得先把左右孩子的子树和都求出来,所以继续往下钻。
- 8叶子 10递归走到节点 10(紫色),它没有孩子,是叶子。叶子的子树就是它自己一个人,下一帧直接结算。
- 9子树和 = 10节点 10 的左右子树和都齐了,结算:子树和 = 10。把 10 记进哈希表,它是第一次出现,计数记 1。节点变绿表示已经处理完。
- 10叶子 40递归走到节点 40(紫色),它没有孩子,是叶子。叶子的子树就是它自己一个人,下一帧直接结算。
- 11子树和 = 40节点 40 的左右子树和都齐了,结算:子树和 = 40。把 40 记进哈希表,它是第一次出现,计数记 1。节点变绿表示已经处理完。
- 12子树和 = 30 + 10 + 40 = 80节点 30 的左右子树和都齐了,结算:子树和 = 30 + 10 + 40 = 80。把 80 记进哈希表,它是第一次出现,计数记 1。节点变绿表示已经处理完。
- 13叶子 -10递归走到节点 -10(紫色),它没有孩子,是叶子。叶子的子树就是它自己一个人,下一帧直接结算。
- 14子树和 = (-10)节点 -10 的左右子树和都齐了,结算:子树和 = (-10)。把 -10 记进哈希表,它是第一次出现,计数记 1。节点变绿表示已经处理完。
- 15子树和 = 20 + 80 + (-10) = 90节点 20 的左右子树和都齐了,结算:子树和 = 20 + 80 + (-10) = 90。把 90 记进哈希表,它是第一次出现,计数记 1。节点变绿表示已经处理完。
- 16进入 80递归进入节点 80(紫色)。它还有孩子,子树和现在算不了,得先把左右孩子的子树和都求出来,所以继续往下钻。
- 17叶子 40递归走到节点 40(紫色),它没有孩子,是叶子。叶子的子树就是它自己一个人,下一帧直接结算。
- 18子树和 = 40节点 40 的左右子树和都齐了,结算:子树和 = 40。把 40 记进哈希表,这个和之前出现过,计数涨到 2。节点变绿表示已经处理完。
- 19叶子 10递归走到节点 10(紫色),它没有孩子,是叶子。叶子的子树就是它自己一个人,下一帧直接结算。
- 20子树和 = 10节点 10 的左右子树和都齐了,结算:子树和 = 10。把 10 记进哈希表,这个和之前出现过,计数涨到 2。节点变绿表示已经处理完。
- 21子树和 = 80 + 40 + 10 = 130节点 80 的左右子树和都齐了,结算:子树和 = 80 + 40 + 10 = 130。把 130 记进哈希表,它是第一次出现,计数记 1。节点变绿表示已经处理完。
- 22子树和 = 50 + 90 + 130 = 270节点 50 的左右子树和都齐了,结算:子树和 = 50 + 90 + 130 = 270。把 270 记进哈希表,它是第一次出现,计数记 1。节点变绿表示已经处理完。
- 23最大次数 = 2九个节点全部结算完,计数表也满了。现在扫一遍这张表,找出现次数最大的那个。可以看到 10 和 40 都出现了 2 次,其余的和都只出现 1 次,所以最大次数是 2。
- 24答案 = [10, 40]次数等于 2 的和有两个:10 和 40,它俩并列最多。题目要求把出现次数最多的和全部返回,所以答案是 [10, 40]。这也提醒我们:最多的可能不止一个,别只返回一个就交卷。
⚠️ 容易写错的地方
✗ 错:用前序/层序去算子树和
✓ 对:必须后序:先有左右子树和才能算父亲
父节点的和依赖孩子,孩子没算完父亲就是错的
✗ 错:只返回出现次数最多的一个和
✓ 对:并列最多的要全部返回
本题可能有多个和并列第一,漏返回会判错
✗ 错:忘了叶子也要计数
✓ 对:每个节点(含叶子)的子树和都要记一笔
叶子的子树和就是它本身,同样参与统计
完整代码(Python / C++ / Java)
Python
from __future__ import annotations
from 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 = right
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class 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])C++
#include <algorithm>
#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <deque>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <optional>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace std;
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
class Solution {
public:
vector<int> findFrequentTreeSum(TreeNode* root) {
unordered_map<int, int> cnt;
function<int(TreeNode*)> dfs = [&](TreeNode* node) {
if (!node) return 0;
int s = node->val + dfs(node->left) + dfs(node->right);
++cnt[s];
return s;
};
dfs(root);
int best = 0;
for (auto& [_, c] : cnt) best = max(best, c);
vector<int> ans;
for (auto& [s, c] : cnt) if (c == best) ans.push_back(s);
sort(ans.begin(), ans.end());
return ans;
}
};Java
import java.util.*;
class TreeNode { int val; TreeNode left; TreeNode right; TreeNode(){} TreeNode(int val){this.val=val;} TreeNode(int val, TreeNode left, TreeNode right){this.val=val;this.left=left;this.right=right;} }
class ListNode { int val; ListNode next; ListNode(){} ListNode(int val){this.val=val;} ListNode(int val, ListNode next){this.val=val;this.next=next;} }
class Solution {
private Map<Integer, Integer> cnt;
public int[] findFrequentTreeSum(TreeNode root) {
cnt = new HashMap<>();
dfs(root);
int best = 0;
for (int c : cnt.values()) best = Math.max(best, c);
ArrayList<Integer> ans = new ArrayList<>();
for (Map.Entry<Integer, Integer> e : cnt.entrySet()) if (e.getValue() == best) ans.add(e.getKey());
Collections.sort(ans);
int[] out = new int[ans.size()];
for (int i = 0; i < ans.size(); i++) out[i] = ans.get(i);
return out;
}
private int dfs(TreeNode node) {
if (node == null) return 0;
int s = node.val + dfs(node.left) + dfs(node.right);
cnt.put(s, cnt.getOrDefault(s, 0) + 1);
return s;
}
}复杂度
时间
O(n)
每个节点只在后序里被访问、结算一次,哈希表的记一笔和最后扫表都是均摊 O(1)
空间
O(n)
哈希表最多存 n 个不同的子树和;递归栈深为树高 h,最坏(链状树)也是 O(n)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 出现次数最多的子树元素和 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
如果树非常深,递归会不会爆栈?怎么办?+
极端链状树深度可达 n,递归栈会很深。工程上可以改成显式栈的迭代后序遍历,或者用带父指针的方式自底向上结算,避免系统调用栈过深。面试讲清「后序的本质是先子后父」并能说出迭代替代方案即可。
怎么一次遍历就同时拿到最大次数和答案?+
可以在 DFS 结算每个和、更新哈希计数的同时,维护一个全局最大次数 best 和对应的和集合:若某个和的新计数大于 best 就清空集合重设,等于 best 就加入。这样遍历结束直接得到答案,省掉最后再扫一遍哈希表的步骤。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 出现次数最多的子树元素和 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。