LeetCode501. 二叉搜索树中的众数

501. 二叉搜索树中的众数


一、题目

给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。

如果树中有不止一个众数,可以按 任意顺序 返回。

假定 BST 满足如下定义:

  • 结点左子树中所含节点的值 小于等于 当前节点的值
  • 结点右子树中所含节点的值 大于等于 当前节点的值
  • 左子树和右子树都是二叉搜索树

示例 1:

img

输入:root = [1,null,2,2]
输出:[2]

示例 2:

输入:root = [0]
输出:[0]

提示:

  • 树中节点的数目在范围 [1, 104]
  • -105 <= Node.val <= 105

**进阶:**你可以不使用额外的空间吗?(假设由递归产生的隐式调用栈的开销不被计算在内)

二、题解
方法一:统计频率并排序

算法思路

  1. 首先,题目给出了一个二叉搜索树(BST),我们知道在BST中,每个节点的值大于等于其左子树中的节点值,且小于等于其右子树中的节点值。
  2. 我们需要找出BST中的众数,即出现频率最高的元素。可以理解为找出出现次数最多的节点值。
  3. 我们可以使用递归来遍历BST的所有节点,并统计每个节点值出现的次数。这里采用前序遍历,从根节点开始遍历。
  4. 我们可以使用一个 map<int, int> 数据结构来记录每个节点值出现的次数。其中,键是节点值,值是该节点值出现的次数。
  5. 接下来,需要对统计结果进行处理,找出出现次数最多的节点值。可以使用一个 vector<pair<int, int>> 来存储键值对,然后根据值(出现次数)进行排序。
  6. 最后,我们从排序后的 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)。
方法二:中序遍历递归

算法思路

  1. 首先,在BST中,每个节点的值大于等于其左子树中的节点值,小于等于其右子树中的节点值。
  2. 可以采用递归中序遍历BST的方式,遍历整个树,同时记录当前节点值的出现次数以及最大频率。
  3. 在遍历过程中,我们使用一个辅助变量 num 来记录当前节点值的出现次数,使用 maxFeq 变量来记录最大的出现频率,以及一个 pre 变量来记录上一个遍历到的节点,以便比较当前节点值与上一个节点值是否相同。
  4. 当我们遍历到一个节点时,我们首先递归遍历其左子树,然后根据 pre 变量比较当前节点值与上一个节点值。如果相同,num 计数递增,否则将 num 重置为 1。
  5. 然后我们与 maxFeq 进行比较,如果 num 大于 maxFeq,说明找到了一个新的最大频率,我们将 maxFeq 更新为 num,并将结果数组 result 清空,然后将当前节点值添加进去。
  6. 如果 nummaxFeq 相等,我们只需将当前节点值添加到结果数组中,因为它也是众数之一。
  7. 最后,我们递归遍历右子树,完成整个遍历过程。

具体实现

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; // 返回众数数组
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KeepCoding♪Toby♪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值