LeetCode 703:数据流中的第K大元素

LeetCode 703:数据流中的第K大元素

题目描述

设计一个找到数据流中第K大元素的类(class)。注意是排序后的第K大元素,不是第K个不同的元素。

你的 KthLargest 类需要一个同时接收整数 k 和整数数组nums 的构造器,它包含数据流中的初始元素。每次调用 KthLargest.add,返回当前数据流中第K大的元素。

示例:

int k = 3;
int[] arr = [4,5,8,2];
KthLargest kthLargest = new KthLargest(3, arr);
kthLargest.add(3); // returns 4
kthLargest.add(5); // returns 5
kthLargest.add(10); // returns 5
kthLargest.add(9); // returns 8
kthLargest.add(4); // returns 8

解题

二叉搜索树

    这道题在二叉搜索树专栏里出现的,所以一开始就硬搞了个用二叉搜索树的方法,这也印证了某位所说的,“刷leetcode最好从头到尾地刷,因为专栏会给提示,这会影响学习的效果”,但我觉得对于不熟悉的某个部分,刷一下专栏,一方面整个专栏下来,对某种数据结构或者方式方法的使用会变得熟悉,另一方面,会了解某种数据结构或者方式方法应该怎么用,在什么情况下会使用到。
    以上是题外话,先说用二叉搜索树的思路:如果不考虑有重复元素的话,最容易想到的方法是直接使用二叉搜索树来不断插入元素,寻找第K大元素时,只需要遍历,比如后序遍历法,找到第K大元素即可。
    那么能不能更好地利用二叉搜索树呢?能,但是需要每个节点保存以它为根节点地子树的节点的数目。对于任意一一个节点,假如我们要寻找该节点为根节点的子树中第K大元素,应该分为以下情况:
    如果其右子树总节点数目大于等于K,那么第K大元素就在右子树中,则应该在右子树中寻找第K大元素。
    如果右子树总节点数N小于K,那么第K大元素在剩下的元素中排第(K-N),如果(K-N)小于或等于当前节点对应的元素的数目,则当前节点为目标元素;
    如果(K-N)大于当前节点对应元素的数目M,则目标元素在剩下的元素中排第(K-N-M),然后再在左子树中寻找第(K-N-M)大元素。
    这样从根节点出发,以此迭代下去就可以找到第K大元素。在实际解答中,可以保存上一次的第K大元素,如果插入的元素小于第K大元素,则直接返回上一次的结果即可,而且无需插入,因为对当前树来说没有任何意义。


class KthLargest {
public:
    struct  TreeNode{
        int val;
        int n_num;//该节点共有子数数量
        TreeNode *left;
        TreeNode *right;
        TreeNode() : val(0),n_num(1), left(nullptr), right(nullptr) {}
        TreeNode(int x) : val(x),n_num(1), left(nullptr), right(nullptr) {}
        TreeNode(int x, TreeNode *left, TreeNode *right) : 
        val(x),n_num(1), left(left), right(right) {}
    };
    void add_list(int val){
        if (root==NULL) {
            root = new TreeNode(val);
            return;
        }
        TreeNode *cur_node = root;
        while (cur_node){
            cur_node->n_num += 1;
            if (cur_node->val == val) return;
            
            if (cur_node->val > val){
                if (cur_node->left) cur_node = cur_node->left;
                else{
                    cur_node->left = new TreeNode(val);
                    return;
                }             
            }
            else{
                if (cur_node->right) cur_node = cur_node->right;
                else{
                    cur_node->right = new TreeNode(val);
                    return;
                }
            }
        }
    }
    KthLargest(int k, vector<int>& nums):kth(k), root(NULL),kth_max(INT_MIN) {
        for (auto &i : nums){
            add_list(i);
        }
    }
    
    int add(int val) {
        int k = kth;
        if (kth_max < val){
            add_list(val);
            TreeNode *cur_node = root;
            while (cur_node){
                while (cur_node->right && cur_node->right->n_num >= k)
                    cur_node = cur_node->right;
                if (cur_node->left==NULL || 
                k <= (cur_node->n_num - cur_node->left->n_num)) {
                    kth_max = cur_node->val;
                    return cur_node->val;
                }
                else{ 
                    k -= cur_node->n_num;
                    if (cur_node->left) k += cur_node->left->n_num;
                    cur_node = cur_node->left;
                }
            }
        }
        else return kth_max;
        return kth_max;
    }
private:    
    TreeNode *root;
    int kth;
    int kth_max;
};
};

/**
 * Your KthLargest object will be instantiated and called as such:
 * KthLargest* obj = new KthLargest(k, nums);
 * int param_1 = obj->add(val);
 */
multiset

    借助multiset对元素进行排序,而且multiset的排序依靠的就是红黑树,通过使用multiset可以避免二叉树的维护(而且更高效)。在上一个方法中,小于第K大元素的无需插入,在这里可以更简单,我们只需要维护multiset的大小不超过K,即如果multiset的大小小于K时,元素可以任意插入;如果multiset等于K时,插入一个新的元素之前,首先要对比该元素与multiset第一个(即第K大)元素比较,如果待插入元素更大,则插入该元素的同时,删除第一个元素,新的第一个元素即为新的第K大元素。

class KthLargest {
public:
    KthLargest(int k, vector<int>& nums):kth(k) {
        for (const auto &i : nums){
            ordered_nums.insert(i);
            if (ordered_nums.size() > kth)
                ordered_nums.erase(ordered_nums.begin());
        }
    }
    
    int add(int val) {
        if (ordered_nums.size() < kth || *ordered_nums.begin() < val){
            ordered_nums.insert(val);
            if (ordered_nums.size() > kth) 
                ordered_nums.erase(ordered_nums.begin());
        }
        return *ordered_nums.begin();
    }
private:
    multiset<int> ordered_nums;
    int kth;
};

/**
 * Your KthLargest object will be instantiated and called as such:
 * KthLargest* obj = new KthLargest(k, nums);
 * int param_1 = obj->add(val);
 */

优先队列

    优先队列可以简单地认为具有排序功能的队列,其默认大顶堆,即最大的元素放堆顶,也就是降序,这里使用时应设定为增序。与使用multiset的方法相同,我们只需要将该优先队列的大小维护在不超过K即可,对于一个新的元素,是否插入以及插入的处理过程与上一个方法相同。

class KthLargest {
public:
    KthLargest(int k, vector<int>& nums):kth(k) {
        for (const auto &i : nums){
            g_queue.emplace(i);
            if (g_queue.size() > kth)
                g_queue.pop();
        }
    }
    
    int add(int val) {
        if (g_queue.size() < kth || g_queue.top() < val){
            g_queue.emplace(val);
            if (g_queue.size() > kth) 
                g_queue.pop();
        }
        return g_queue.top();
    }
private:
    priority_queue<int, vector<int>, greater<int>> g_queue;
    int kth;
};

/**
 * Your KthLargest object will be instantiated and called as such:
 * KthLargest* obj = new KthLargest(k, nums);
 * int param_1 = obj->add(val);
 */
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值