问题:
有一个特殊的日子,当< = 1'' 000事件被举行。
有< = 1' 000&000; 39%的观众观看这些事件(不一定是相同的数字)。
特殊日子分为< = 100� 000时刻(每个活动持续一整天)。
每个观众在特殊日子里正在观看一个事件。
从第一刻到第二刻,他们只观看一大块事件。
I / O:
首先,我们给出了观众人数N和时刻数。
然后,我们给了N行。第i行包含三个数字:
M1,M2,E - 告诉我们第i个观众将从时刻M1到M2(包括)时刻观看事件E.
然后,我们得到问题。每个问题都是一个数字Q.它询问当前Q中最多的事件是什么。(如果有多个事件,则返回最低数字的事件)
考虑到内存限制(128MB),回答这些问题的快速(est)算法是什么?
答案 0 :(得分:0)
我能想到的最快(现在)是按事件排序N行。这具有复杂度O(N log(N))。要回答一个问题,对已排序的数组行进行单次扫描就足够了。这具有复杂度O(N)。
内存要求是(假设4字节整数):
答案 1 :(得分:0)
以下解决方案具有构建数据结构的复杂度O(n log n)。每个查询都可以在O(1)中得到满足。
创建一个类Viewer
来表示您的输入行。那就是:
class Viewer
int StartMoment;
int EndMoment;
int EventId;
还要创建一个类Event
,其中包含事件编号及其计数:
class Event
int Id;
int Count;
创建{Id}键入的Event
个实例字典。还要创建Event
的优先级队列(最大堆),按Count
排序,降序和Id
。请注意,优先级队列包含对字典中项目的引用。此外,您的优先级队列必须有效地实现 reduce-key 和删除(即删除任意项)。像配对堆这样的东西对此有好处。它可以使用二进制堆,但很难实现。
字典就在那里,您可以轻松查找优先级队列中的内容。这使得它非常快,O(1)可以在队列中定位,并且可以非常快速地进行删除和重新排序。调用字典EventsDict
。优先级队列为EventsQueue
。两者最初都是空的。
创建另一个优先级队列,这是Viewer
个实例之一,由EndMoment
排序。称之为CurrentViewers
。它也是空的。
最后,创建一个moment-count
Event
引用数组。这是下面算法的输出。称之为Moments
。
将查看器行读入Viewer
实例列表。称之为Viewers
。按StartMoment
和EventId
排序。
现在开始按顺序浏览Viewer
行。
int currentMoment;
for each viewer
{
Event currentEvent;
if (viewer.StartMoment > currentMoment)
{
// The most viewed event at the current moment is at the top of the CurrentEvents heap
Moments[currentMoment] = EventsQueue.Peek();
currentMoment = viewer.StartMoment;
// remove any viewers that are no longer active, and update the events queue accordingly
while (CurrentViewers.Peek().EndMoment < currentMoment)
{
oldViewer = CurrentViewers.Dequeue();
currentEvent = EventsDict[oldViewer.EventId];
EventsQueue.Remove(currentEvent);
--EventsQueue.Count;
// if the count goes to 0, remove it from the active list
// Otherwise re-insert it into the queue
if (currentEvent.Count == 0)
{
EventsDict.Remove(currentEvent.Id);
}
else
{
EventsQueue.Enqueue(currentEvent);
}
}
}
if (EventsDict.ContainsKey(viewer.EventId))
{
// updates the count and re-inserts the item in the queue
currentEvent = EventsDict[viewer.EventId];
EventsQueue.Remove(currentEvent);
++currentEvent.Count;
EventsQueue.Enqueue(currentEvent);
}
else
{
currentEvent = new Event(viewer.EventId, 1);
EventsQueue.Enqueue(currentEvent);
}
// Add this viewer to the `CurrentViewers` queue
CurrentViewers.Enqueue(viewer);
}
工作原理:
这里的想法是,我们从拥有观众的最早时刻开始,我们跟踪正在观看哪些事件。在读取每个查看器记录时,我们更新引用事件的计数并将查看器添加到队列中,以便在到达其EndMoment时将其删除。维护EventsQueue,使得当前具有最大数量的查看者的事件始终位于堆的顶部。这就是为什么我们每次计数更改时删除并重新添加事件的原因。 EventsDict仅用于跟踪当前活动的事件。
所以我们假设我们有这五位观众:
eventId startMoment endMoment
7 2 7
1 3 5
1 3 4
7 3 5
7 4 6
在循环开始时,currentMoment
设置为2,因为它是第一个观看事件的开始时刻。第一次通过循环,事件7被添加到事件队列和currentViewers队列。
下一次循环时,viewer.StartMoment
为3,但currentMoment
为2.所以我们必须更新输出,然后将currentMoment
设置为3.没有任何内容从当前查看器队列,因为没有任何endMoment
小于3.然后我们将事件1添加到当前事件队列,并添加到当前查看器。
下一次循环(上面列表中的第三项),我们仍处于第3阶段。我们增加事件1的计数并添加重新插入到事件队列中。该项目已添加到currentViewers。
下一次,仍在第3时刻。增加事件7的计数并将查看器添加到currentViewers。
最后一项,我们现在正处于第4时刻。此时,事件1和7都有两个观众。事件1具有较低的id,因此该事件将被放置在输出中。我们还需要从当前事件队列中删除列表中的第三项(事件1,结束时刻4),并减少事件1的计数。
然后我们将当前时刻设置为4并增加事件7的计数。
看起来我离开了算法的最后一部分,它必须在读取所有查看器事件后清空队列。因此,在上面的循环结束时,您需要添加更多代码:
currentMoment = currentViewers.Peek().EndMoment;
while (currentViewers is not empty)
{
viewer = currentViewers.Dequeue();
if (viewer.EndMoment > currentMoment)
{
Moments[currentMoment] = EventsQueue.Peek();
currentMoment = viewer.EndMoment;
}
currentEvent = EventsDict[oldViewer.EventId];
EventsQueue.Remove(currentEvent);
--EventsQueue.Count;
// if the count goes to 0, remove it from the active list
// Otherwise re-insert it into the queue
if (currentEvent.Count == 0)
{
EventsDict.Remove(currentEvent.Id);
}
else
{
EventsQueue.Enqueue(currentEvent);
}
}
所有代码都是确保在读取所有查看器后填充Moments数组的尾部。它基本上是代码的重复,当事件视图超出范围时,以及当前时刻发生变化时,它会删除事物。
让我们调用观看者N的数量。对观看者行进行排序是O(N log N)。此外,每个查看器都会添加到CurrentViewers队列一次(O(1)),并删除一次(O(log N))。每次循环都涉及插入或删除一个事件,然后重新插入EventsQueue。对于最坏情况分析,让我们说通过循环的evey时间涉及从EventsQueue中删除项目的O(log n)。
所以我们有O(N log N)作为初始排序。 O(N)将项添加到CurrentViewers队列。 O(N log N)从CurrentViewers中删除项目。 O(N)将项目添加到EventsQueue,O(N log N)将项目从EventsQueue中删除。所以施工是:
3*(N log N) + 2*N ~ O(N log N)
您最终会构建一个每时每刻都观看次数最多的数组。因此,单个查询是一个简单的数组查找,O(1)。
观看者和活动少于1,000,000,时间少于100,000,这应该很容易适合128 MB。如果它只占用了一半,我会感到非常惊讶。