501. 二叉搜索树中的众数
文章目录
一、题目
给你一个含重复值的二叉搜索树(BST)的根节点 root
,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。
如果树中有不止一个众数,可以按 任意顺序 返回。
假定 BST 满足如下定义:
- 结点左子树中所含节点的值 小于等于 当前节点的值
- 结点右子树中所含节点的值 大于等于 当前节点的值
- 左子树和右子树都是二叉搜索树
示例 1:
输入:root = [1,null,2,2]
输出:[2]
示例 2:
输入:root = [0]
输出:[0]
提示:
- 树中节点的数目在范围
[1, 104]
内 -105 <= Node.val <= 105
**进阶:**你可以不使用额外的空间吗?(假设由递归产生的隐式调用栈的开销不被计算在内)
二、题解
方法一:统计频率并排序
算法思路
- 首先,题目给出了一个二叉搜索树(BST),我们知道在BST中,每个节点的值大于等于其左子树中的节点值,且小于等于其右子树中的节点值。
- 我们需要找出BST中的众数,即出现频率最高的元素。可以理解为找出出现次数最多的节点值。
- 我们可以使用递归来遍历BST的所有节点,并统计每个节点值出现的次数。这里采用前序遍历,从根节点开始遍历。
- 我们可以使用一个
map<int, int>
数据结构来记录每个节点值出现的次数。其中,键是节点值,值是该节点值出现的次数。 - 接下来,需要对统计结果进行处理,找出出现次数最多的节点值。可以使用一个
vector<pair<int, int>>
来存储键值对,然后根据值(出现次数)进行排序。 - 最后,我们从排序后的
vec
中取出第一个元素,即出现次数最多的节点值。然后再遍历一次vec
,将出现次数与第一个元素相同的节点值添加到结果数组中。
具体实现
首先,定义一个辅助函数 findFeq
,用于递归遍历BST并统计节点值的出现次数。
void findFeq(TreeNode *root, map<int, int> &feq) {
if (root == nullptr) {
return;
}
feq[root->val]++;
findFeq(root->left, feq);
findFeq(root->right, feq);
}
然后,定义另一个辅助函数 cmp
,用于在排序时比较键值对的值,使其按照值(出现次数)降序排列。
bool static cmp(pair<int, int> &a, pair<int, int> &b) {
return a.second > b.second;
}
最后,编写 findMode
函数来实现整个算法流程:
vector<int> findMode(TreeNode* root) {
map<int, int> feq;
findFeq(root, feq);
vector<pair<int, int>> vec(feq.begin(), feq.end());
sort(vec.begin(), vec.end(), cmp);
vector<int> result;
result.push_back(vec[0].first);
for (int i = 1; i < vec.size(); i++) {
if (vec[i].second == vec[0].second) {
result.push_back(vec[i].first);
}
}
return result;
}
整体代码如下
/**
* Definition for a binary tree node.
* 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) {}
* };
*/
class Solution {
public:
void findFeq(TreeNode *root, map<int,int>& feq){
//这里采用前序遍历方式
if(root == nullptr){
return;
}
feq[root->val]++;
findFeq(root->left, feq);
findFeq(root->right, feq);
}
bool static cmp(pair<int,int> &a, pair<int,int> &b){
return a.second > b.second;
}
vector<int> findMode(TreeNode* root) {
map<int,int> feq;
findFeq(root,feq);
vector<pair<int,int>> vec(feq.begin(),feq.end());
sort(vec.begin(),vec.end(),cmp);
vector<int> result;
result.push_back(vec[0].first);
for(int i = 1; i < vec.size(); i++){
if(vec[i].second == vec[0].second){
result.push_back(vec[i].first);
}
}
return result;
}
};
算法分析
- 时间复杂度:遍历BST的时间复杂度为 O(n),其中 n 为节点个数,统计节点值出现次数的时间复杂度也是 O(n),排序的时间复杂度是 O(nlogn),所以总体时间复杂度是 O(nlogn)。
- 空间复杂度:递归调用的隐式栈空间为 O(h),其中 h 为树的高度,额外的空间复杂度为 O(n)(用于存储节点值的出现次数和排序结果),所以总体空间复杂度是 O(n)。
方法二:中序遍历递归
算法思路
- 首先,在BST中,每个节点的值大于等于其左子树中的节点值,小于等于其右子树中的节点值。
- 可以采用递归中序遍历BST的方式,遍历整个树,同时记录当前节点值的出现次数以及最大频率。
- 在遍历过程中,我们使用一个辅助变量
num
来记录当前节点值的出现次数,使用maxFeq
变量来记录最大的出现频率,以及一个pre
变量来记录上一个遍历到的节点,以便比较当前节点值与上一个节点值是否相同。 - 当我们遍历到一个节点时,我们首先递归遍历其左子树,然后根据
pre
变量比较当前节点值与上一个节点值。如果相同,num
计数递增,否则将num
重置为 1。 - 然后我们与
maxFeq
进行比较,如果num
大于maxFeq
,说明找到了一个新的最大频率,我们将maxFeq
更新为num
,并将结果数组result
清空,然后将当前节点值添加进去。 - 如果
num
与maxFeq
相等,我们只需将当前节点值添加到结果数组中,因为它也是众数之一。 - 最后,我们递归遍历右子树,完成整个遍历过程。
具体实现
class Solution {
public:
TreeNode *pre = nullptr; // 上一个遍历到的节点
int maxFeq = 0; // 最大频率
void findFeq(vector<int>& result, TreeNode *cur, int &num) {
if (cur == nullptr) {
return;
}
findFeq(result, cur->left, num); // 递归遍历左子树
if (pre) {
if (pre->val != cur->val) {
num = 1;
} else {
num++;
}
} else {
num = 1;
}
pre = cur;
if (num > maxFeq) {
result.clear(); // 清空结果数组
result.push_back(cur->val);
maxFeq = num;
} else if (num == maxFeq) {
result.push_back(cur->val);
}
findFeq(result, cur->right, num); // 递归遍历右子树
}
vector<int> findMode(TreeNode* root) {
vector<int> result;
int num = 0; // 当前节点值的出现次数
findFeq(result, root, num); // 开始递归遍历
return result; // 返回众数数组
}
};
算法分析
- 时间复杂度:由于遍历了整个BST一次,时间复杂度为 O(n),其中 n 是节点数。
- 空间复杂度:递归调用的隐式栈空间为 O(h),其中 h 是树的高度。额外的空间复杂度主要用于结果数组和辅助变量,是 O(n)。整体的空间复杂度是 O(n)。
方法三:中序遍历迭代
class Solution {
public:
vector<int> findMode(TreeNode* root) {
stack<TreeNode*> st; // 使用栈来辅助进行迭代遍历
TreeNode* cur = root; // 当前节点指针,初始化为根节点
TreeNode* pre = NULL; // 用于记录上一个访问过的节点
int maxCount = 0; // 记录最大的频率
int count = 0; // 记录当前节点值的出现频率
vector<int> result; // 存放结果数组,即众数数组
// 迭代遍历二叉树
while (cur != NULL || !st.empty()) {
if (cur != NULL) { // 如果当前节点非空
st.push(cur); // 将当前节点入栈
cur = cur->left; // 继续向左子树遍历,模拟递归
} else {
cur = st.top();
st.pop();
// 中序遍历:比较当前节点值与前一个节点值
if (pre == NULL) { // 如果是第一个节点
count = 1;
} else if (pre->val == cur->val) { // 如果与前一个节点值相同
count++;
} else { // 如果与前一个节点值不同
count = 1;
}
// 更新结果数组和最大频率
if (count == maxCount) { // 如果与最大频率相同,加入结果数组
result.push_back(cur->val);
}
if (count > maxCount) { // 如果频率更大,更新结果数组和最大频率
maxCount = count;
result.clear(); // 清空之前的结果数组,因为之前的结果已经失效
result.push_back(cur->val);
}
pre = cur; // 更新上一个节点
cur = cur->right; // 继续向右子树遍历
}
}
return result; // 返回众数数组
}
};