您的位置:首页 > 其它

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

2017-02-10 12:53 375 查看
原问题(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 << " ";

4000
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
;
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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息