Kth largest element in a stream
Design a class to find the kth largest element in a stream. Note that it is the kth largest element in the sorted order, not the kth distinct element.
Your KthLargest
class will have a constructor which accepts an integer k
and an integer array nums
, which contains initial elements from the stream. For each call to the method KthLargest.add
, return the element representing the kth largest element in the stream.
寻找一个序列中的第K个最大的数,其中有add函数和类函数的预定义。
该问题看似简单,但是考虑到时间复杂度的话,普通的算法n的平方就难以接受了。涉及到有序查找,故考虑使用排序二叉树。我们知道,排序二叉树按照中序遍历即为升序序列,但如果反着来,从整个二叉树的最右边结点开始依次向左寻找,代码如下:
BiTreeNode *p;
p=root;
BiTreeNode *Mystack[1000];
int top=0;
while(p!=NULL||top!=0){ //在指针不为空或者栈不为空时,进行循环。
while(p!=NULL){
Mystack[top++]=p;
p=p->right; //从根节点root开始依次压栈,寻找整个二叉树最右边的结点,也就是数值最大的结点。
}
p=Mystack[--top]; //出栈,访问结点。
cout<<p->val<<" ";
if(p->left!=NULL){
p=p->left; //如果结点有左子树,则开始访问左子树
}
else
p=NULL; //如果没有,则将指针指控,以便于下一次跳过第二层循环,直接出栈。
}
这样一来,在查找过程中设置好标记量K,就可以实现降序查找Kth largest element。但是这种算法还有一个最大的问题,那就是虽然将时间复杂度缩减到了树高,但毕竟要处理的数据太多,排序二叉树建立以后,不管K的值为多少,时间复杂度都为树高,很不划算。那么应该如何改进算法呢?
我们注意到,查找 Kth largest element ,其实就是在有序序列中排除掉N-K个小的数,然后输出下一个即可。根据排序二叉树特点,我们依次从根节点排除,其中小于 Kth largest element 的数的个数,可以由经过的每个结点的左子树的数目累加来得到。大致思路为从根结点一直向右寻找,在寻找过程中依次减去所经过结点的左子树的结点数,判断所减去的结点数目和需要减去的数目的关系来得到 Kth largest element。具体代码参考如下:
class BiTreeNode{ //二叉树结点定义
public:
int rep;
int leftCount;
int val;
BiTreeNode *left;
BiTreeNode *right;
BiTreeNode(){
rep=0;
leftCount=0;
left=NULL;
right=NULL;
}
};
void InsertBiTree(BiTreeNode *root,int val){ //排序二叉树的建立,同时记录每个结点的左子树
的数目。
if(root->val==val){
root->rep++; //rep:需要插入的结点数值与所访问到的结点数值相等,则不再插入,将所访
问结点的rep属性值++。
root->leftCount++; //同时将leftCount属性值++。即将相等的数值也计入左子树结点数。
return ;
}
else if(root->val<val){
if(root->right!=NULL)
InsertBiTree(root->right,val);
else{
BiTreeNode *right=new BiTreeNode();
right->val=val;
root->right=right;
return ;
}
}
else{
root->leftCount++; //小于所访问结点的数值,则该结点左子树结点数++。
if(root->left!=NULL)
InsertBiTree(root->left,val);
else{
BiTreeNode *left=new BiTreeNode();
left->val=val;
root->left=left;
return ;
}
}
return;
}
class KthLargest {
public:
BiTreeNode *SearchTree;
int k;
int node;
KthLargest(int k, vector<int> nums) { //KthLargest 类构造函数。
node=0;
if(nums.size()==0){
SearchTree=NULL;
this->k=k;
return ;
}
else{
BiTreeNode *root=new BiTreeNode();
root->val=nums[0];
node++;
for(int i=1;i<nums.size();i++){ //将初始化序列构造为排序二叉树。
InsertBiTree(root,nums[i]);
node++;
}
SearchTree=root;
this->k=k;
}
}
int FindKth(BiTreeNode *root,int k){ //重头戏来了!
int EndFlag=node-k; //EndFlag为需要减掉的结点数
BiTreeNode *p;
p=root;
if(p!=NULL){
while(EndFlag!=0||p==root){ // p==root:为了查找第N个最大的数
if(p->leftCount<EndFlag){
EndFlag=EndFlag-p->leftCount-1; //该结点还不够减
p=p->right; //向该结点的右子树战略转移!为什么不用判断p是否具有右子树就贸然赋值?因为若p没有右子树,则证明p为最大值,则减掉的结点数为整个树的所有结点。
}
else if(p->leftCount==EndFlag){ // 如果p的左子树的数目正好等于还需要减的数目,则p就是下一个数了,即输出p。
return p->val;
}
else{
if(p->leftCount-p->rep<=EndFlag) // 因为考虑到在序列中还有相等的数存在,所以要考虑真正小于该结点的数目与还需要减去的结点数的关系。
return p->val; // 如果真正小于该结点的数目比还需减去的结点数小,则 Kth largest element 必然落到该结点的数值上。
else{
p=p->left; //如果真正小于该结点的数目大于还需减去的结点数,说明要往该结点的真正的左子树继续探寻。
}
}
}
while(p->left!=NULL)
p=p->left; // 退出循环:即p!=root&&EndFlag==0,这时说明已经减去了需要减去的结点数,
//但不能贸然输出,因为无法保证返回的p即为真正序列中的后继。
return p->val; // 找到真实后继后,则输出。
}
else
return -1234; //输入的K值不合法,输出错误代码。
}
int add(int val) { //调用寻找函数
if(SearchTree==NULL){ // 若初始序列为空,则建立根结点。
BiTreeNode *temp=new BiTreeNode();
temp->val=val;
SearchTree=temp;
node++;
return SearchTree->val;
}
else{
InsertBiTree(SearchTree,val); // 先插入结点
node++;
cout<<"root->val= "<<SearchTree->val<<" root->leftCount= "<<SearchTree->leftCount<<endl;
return FindKth(SearchTree,k); //再寻找 Kth largest element。
}
}
};
/**
* Your KthLargest object will be instantiated and called as such:
* KthLargest obj = new KthLargest(k, nums);
* int param_1 = obj.add(val);
*/
与此算法同理的还有:从根结点一直向左寻找,同时减去右子树结点数目,判断需要减去的数目和已经减去的数目的关系,来决定寻找方向和输出值,不在话下。