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);
*/