在我的考试中,我遇到了这个问题。
网站将电影流式传输到客户的电视或其他设备。电影属于动作,戏剧,神秘等几种类型中的一种。每部电影都属于一种类型(因此,如果一部电影是一部动作片和一部喜剧,它就属于一种叫做“动作 - 喜剧”的类型。 “)。该网站拥有约1000万客户,约25,000部电影,但两者都在快速增长。该网站希望跟踪最流行的电影。您已被聘为首席工程师,负责制定跟踪计划。
i)每次将电影流式传输给客户时,其名称(例如“Harold and Kumar:Escape from Guantanamo Bay”)和类型(“喜剧”)都会发送到您的程序,以便它可以更新数据结构维护。
(假设您的程序可以在O(1)时间内调用适当的Java类来获取当前年份。)
ii)此外,每隔一段时间,客户就想知道y年代流行音乐中流媒体最多的电影是什么。 (如果y是当前年份,那么会计核算将在当前日期之前完成。)例如,2010年最流行的十大喜剧电影是什么?这里k = 10,g =“comeday”,y = 2010.此查询将发送到您的程序,该程序应输出前k个电影名称。
描述用于实现这两个要求的数据结构和算法。对于(i),分析大O运行时间来更新数据结构,以及(ii)大O运行时间来输出前k个流电影。
我的思维过程是创建一个哈希表,每个新影片都添加到链接列表中哈希表中的各自类型。至于第二部分,我唯一的想法是保持链表排序,但这似乎太昂贵了。什么是更好的选择?
答案 0 :(得分:1)
我使用堆来跟踪类的前k个对象(k固定)。您可以在任何CS文本中找到此数据结构的详细信息,但基本上它是一个二叉树,其中每个节点都小于其子节点。我们将调用reheap(node)
的主要操作假定node
的子节点都是堆,将node
与其两个子节点中的较小节点进行比较,如果需要则进行交换,并递归调用修改后的孩子reheap
。该类需要有一个重载的operator<
或等效的定义来执行此操作。
在任何时候,堆都会在堆顶部保存最小的k个对象。当一个新对象到达时,它比堆顶部大,它会替换堆上的那个对象,然后
调用reheap
。如果堆上已有的对象变得比其较小的子对象大,那么这也可能发生在顶级节点以外的节点上。如果堆上的对象变得比其父对象小(在您描述的情况下可能不会发生这种情况),则会发生另一种类型的更新。在这里它与父母交换,然后我们递归地与祖父母等进行比较。
所有这些更新都具有复杂度O(log(k))。如果您需要输出从上到下排序的堆,则相同的结构可以及时运行 O(k log(k))。 (这个过程称为heapsort)。
由于交换对象可能很昂贵,我通常会将对象保存在某个固定数组中,并将堆实现为指针数组A
,其中A[i]
的子项为{{{ 1}}和A[2i+1]
。
答案 1 :(得分:1)
你可以在O(1)中使用一个哈希表“HT1”从(genre,year,movie_title)映射到迭代器到(num_times_streamed
,电影标题的哈希表)的链表中。您可以使用迭代器查看列表中的下一个元素是否包含一个更大的流媒体计数,如果是,则在那里插入您的电影标题并将其从另一个表中删除(如果空的可以从列表中删除),否则如果存在哈希表没有其他标题然后递增num_times_streamed
,否则在列表中插入一个新的哈希表并添加您的标题。根据需要更新HT1中迭代器的记录。
请注意,如上所述,当num_times_streamed
值递增时,列表中的操作使用端点或现有迭代器通过不超过一个位置,因此O(1)。
要获得前k个标题,您需要一个从{genre,year}到每个链接列表的哈希表HT2:只需从列表末尾迭代,您将遇到带有电影的哈希表或流媒体数量最多的电影,然后是下一个最高的电影,依此类推。如果年份刚刚更改,您可能找不到k
个条目,无论您喜欢哪个都可以处理。如果在查找电影标题时发现它不存在于HT1中,您可以为该类型添加新列表,并将当前年份添加到HT2。
更直观地说,围绕哈希表(无论是映射还是集合)使用{ }
,围绕链接列表使用[ ]
,围绕分组结构/元组数据使用( )
:
HT2 = { "comedy 2015": [ (1, { "title1", "title2" }),
(2, { "title3" }), <--------\
(4, { "title4" }) ], |
"drama 2012": [ (1, { "title5" }), |
(3, { "title6" }) ], |
... | .
}; | .
| .
HT1 = { "title3", -----------------------------------/ |
"title2", ---------------------------------------/
...
};