基于计数排序求中位数问题的O(1)算法(Fraudulent Activity Notifications问题)

原问题(Fraudulent Activity Notifications)链接:https://www.hackerrank.com/challenges/fraudulent-activity-notifications

Fraudulent Activity Notifications问题摘要:
HackerLand National Bank has a simple policy for warning clients about possible fraudulent account activity. If the amount spent by a client on a particular day is greater than or equal to 2×the client’s median spending for the last d days, they send the client a notification about potential fraud. The bank doesn’t send the client any notifications until they have at least d prior days of transaction data.
Given the value of d and a client’s total daily expenditures for a period of n days, find and print the number of times the client will receive a notification over all n days.

这里使用了计数排序(Counting Sort)的思想,用C++写了一个类CountingHeap(因为这种数据结构有点像一个堆,故命名为CountingHeap,但并不是STL的“堆”数据结构)用于解决该问题。就CountingHeap而言,其更新中位数的算法时间复杂度为O(1),空间复杂度为O(range),之前写的一个版本基于插入排序的算法时间复杂度为O(range),空间复杂度O(range),相比之下速度提高了不少。使用Hackerrank上此问题的Test Case #1测试,原先算法需时5.768秒,当前算法需0.173秒。

CountingHeap中使用curSize指示当前元素总数,size[]数组保存各取值出现次数,形成一个[0,range)的“计数堆”。可以想象成编号为0~range-1的空心柱子,每添加一个数就往那个数对应的柱子里面放一个球,size[]数组保存的就是各空心柱子的球数。例如,初始为空,每个柱子都是空的,先添加一个元素2,就往编号为2的柱子里面放一个球,此时size[2]++,从0变为1。

CountingHeap中使用midA和midB指示两个中位数,并且保持midA<=midB。为什么要使用两个变量来表示中位数呢?因为若当前元素为偶数个,则中位数应该取中间两个数的平均数,而这样可能会带来分数,而使用double类型则不能作为索引访问size[]数组,故取两个变量midA,midB分别表示左侧的中位数和右侧的中位数,则两者之和为中位数的2倍。元素总数为奇数个时,midA=midB;偶数个时,midA在左,midB在右,此时midA的下一个元素就是midB。由于可能存在相同的元素,故使用结构体Mid,number成员表示指示的数字,No成员表示所指数字在同值元素中的索引,如第1个出现则No=0,第2个出现则No=1,以此类推。

CountingHeap中位数的实时更新通过移动这两个元素来实现。这里定义了两个private方法moveLeft(Mid &mid)和moveRight(Mid &mid)。以moveLeft为例,将mid左移一位,若当前所指元素已经是同值元素中最左边的(No=0),则先将number减一,寻找左边紧接着的元素;若当前所指元素不是同值元素中最左边的(No>0),则只需将No减一即可。moveRight同理。

接下来需要做的就是实现public中的addElement(int x)方法和deleteElement(int x)方法,这两个方法均借助moveLeft和moveRight实现。这两个方法总体类似,都需要考虑curSize的奇偶,根据x与midA,midB的相对位置进行移动。相对位置分三种,x < midA,midA < x < midB 与 midB < x,其中中间的情况要根据curSize的奇偶来定。因为当curSize为奇数时,添加或删除元素前的midA和midB所指示的并不是同一个元素,而当curSize为偶数时,添加或删除元素前的midA和midB必定是相等的。这两种情况要分别考虑,奇数的时候中间的情况为midA <= x < midB,而偶数的时候中间的情况为midA <= x <= midB。

添加、删除操作midA,midB移动对应表如下:

操作添加删除
   curSize  
    奇数   
    偶数   
    奇数   
    偶数   
x < A<-B<-AA->B->
A < x < B(1)A-> <-BB->A-><-A (B->)(2)
B < xA->B-><-B<-A

注:A,B分别指midA,midB.
注(1):当curSize为奇数时为A <= x < B,偶数时为A <= x <= B。相应地,奇数时下一栏为B <= x,偶数时下一栏为B < x.
注(2):当B所指元素被删掉时B需要右移,否则B不需要移动

源代码如下:(注释部分用于输出调试信息)

#include <iostream>
#include <fstream>
using namespace std;

class CountingHeap
{
public:
    CountingHeap(){}
    CountingHeap(int inRange): midA(-1,0), midB(inRange,0)
    { 
        range = inRange; 
        size = new int [range];
        for(int i=0;i<range;i++)
            size[i] = 0;    
        curSize = 0;
    }
    ~CountingHeap(){ delete []size;  }

    int getCurSize(){ return curSize; }
    int getMidA(){ return midA.number; }
    int getNoA(){ return midA.No; }
    int getMidB(){ return midB.number; }
    int getNoB(){ return midB.No; }
    int get2Mid(){ return midA.number+midB.number; }

    void printSortedArray()
    {
        cout  << " >" << curSize << ": ";
        for(int i=0;i<range;i++)
            for(int j=0;j<size[i];j++)
                cout << i << " ";
        cout << endl;
    }

    void addElement(int x)
    {
        curSize++;

        size[x]++;

        if(curSize==1)
        {
            midA.number = midB.number = x;
            midA.No = midB.No = 0;
        }
        else if(curSize%2)
        {
            if(x<midA.number)
                moveLeft(midB);
            else if(midA.number<=x && x<midB.number)
            {
                moveLeft(midB);
                moveRight(midA);
            }
            else
                moveRight(midA);
        }
        else
        {
            if(x<midA.number)
                moveLeft(midA);
            else if(midA.number<=x && x<=midB.number)
                moveRight(midB);
            else
                moveRight(midB);
        }
    }

    void deleteElement(int x)
    {
        curSize--;

        size[x]--;

        if(curSize%2)
        {
            if(x<midA.number)
                moveRight(midA);
            else if(midA.number<=x && x<midB.number)
                moveRight(midA);
            else
                moveLeft(midB);
        }
        else
        {
            if(x<midA.number)
                moveRight(midB);
            else if(midA.number<=x && x<=midB.number)
            {
                moveLeft(midA);
                if(size[midB.number]<=midB.No)
                    moveRight(midB);
            }
            else
                moveLeft(midA);
        }
    }

private:
    int *size;
    int range;
    int curSize;

    struct Mid
    {
        int number;
        int No;
        Mid(){}
        Mid(int inNumber, int inNo){ number = inNumber; No = inNo; }
    };
    Mid midA;
    Mid midB;

    void moveLeft(Mid &mid)
    {
        //cout << mid.number << "(" << mid.No << "):left";
        if(mid.No==0)
        {
            mid.number--;
            while(mid.number>=0&&size[mid.number]==0)
                mid.number--;
            mid.No = size[mid.number]-1;
        }
        else
            mid.No--;
        //cout << "->" << mid.number << "(" << mid.No << ")" << endl;
    }

    void moveRight(Mid &mid)
    {
        //cout << mid.number << "(" << mid.No << "):right";
        if(mid.No>=size[mid.number]-1)
        {
            mid.number++;
            while(mid.number<range&&size[mid.number]==0)
                mid.number++;
            mid.No = 0;
        }
        else
            mid.No++;
        //cout << "->" << mid.number << "(" << mid.No << ")" << endl;
    }

};

int main()
{
/*  
    ifstream input("E:\\input01.txt");
    cin.rdbuf(input.rdbuf());
    ofstream output("E:\\output01.txt");
    cout.rdbuf(output.rdbuf());
*/  
    int n,d;
    cin >> n >> d;

    int *ar = new int [n];
    for(int i=0;i<n;i++)
        cin >> ar[i];

    int cnt = 0;
    CountingHeap hp(201);
    for(int i=0;i<n;i++)
    {
        if(hp.getCurSize()<d)
        {
            hp.addElement(ar[i]);
            //cout << i << " " << hp.getMidA() << " " << hp.getMidB() << endl;
        }
        else
        {
            //cout << "(" << ar[i] << "," << hp.get2Mid() << ")" << endl;
            if(ar[i]>=hp.get2Mid())
                cnt++;
            hp.addElement(ar[i]);
            hp.deleteElement(ar[i-d]);
        }
        //hp.printSortedArray();
        //cout << "midA=" << hp.getMidA() << "(" << hp.getNoA() << ")  midB=" << hp.getMidB() << "(" << hp.getNoB() << ")" << endl;
    }
    cout << cnt << endl;

    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值