以下问题的解决方案的大致时间复杂度是多少?如果我们假设由于路径压缩,则对self.find()的每次调用将大致摊销到〜O(1)
问题陈述:
给出一个列表帐户,每个元素帐户[i]是一个字符串列表, 其中第一个元素account [i] [0]是一个名称,其余的 元素是代表帐户电子邮件的电子邮件。
现在,我们希望合并这些帐户。绝对有两个帐户 如果有一些共同的电子邮件属于同一个人 两个帐户。请注意,即使两个帐户具有相同的名称,它们 可能属于不同的人,因为人们可能具有相同的名字。一种 一个人最初可以拥有任意数量的帐户,但他们的所有帐户 帐户肯定具有相同的名称。
合并帐户后,返回以下帐户 格式:每个帐户的第一个元素是名称,其余的 元素是按排序顺序的电子邮件。帐户本身可以 可以以任何顺序退回。
示例:输入:accounts = [[“ John”,“ johnsmith@example.com”, “ john00@example.com”],[“ John”,“ johnnybravo@example.com”],[“ John”, “ johnsmith@example.com”,“ john_newyork@example.com”],[“玛丽”, “ mary@example.com”]]
输出:[[“ John”,'john00@example.com','john_newyork@example.com', 'johnsmith@example.com'],[“ John”,“ johnnybravo@example.com”],[“ Mary”, “ mary@example.com”]]
说明:第一个和第三个约翰与他们是同一个人 拥有通用电子邮件“ johnsmith@example.com”。第二约翰和玛丽 是不同的人,因为他们的电子邮件地址都没有被使用 其他帐户。我们可以以任何顺序返回这些列表,例如 答案[['Mary','mary@example.com'],['John', 'johnnybravo@example.com'],['John','john00@example.com', 'john_newyork@example.com','johnsmith@example.com']]仍为 接受。
class Solution:
def accountsMerge(self, accounts):
"""
:type accounts: List[List[str]]
:rtype: List[List[str]]
"""
owners={}
parents={}
merged=collections.defaultdict(set)
results=[]
for acc in accounts:
for i in range(1,len(acc)):
owners[acc[i]] = acc[0]
parents[acc[i]] = acc[i]
for acc in accounts:
p = self.find(acc[1],parents) #Find parent of the first email in the list.
for i in range(2,len(acc)):
#Perform union find on the rest of the emails across all accounts (regardless of account name, as no common email can exist between different names.)
#Any common emails between any 2 lists will make those 2 lists belong to the same set.
currP = self.find(acc[i],parents)
if p!=currP:
parents[currP] = p
for acc in accounts:
p = self.find(acc[1],parents)
for i in range(1,len(acc)):
merged[p].add(acc[i])
for name,emails in merged.items():
res = [owners[name]] + sorted(emails)
results.append(res)
return results
def find(self,node,parents):
if node!=parents[node]:
parents[node] = self.find(parents[node],parents)
return parents[node]
答案 0 :(得分:0)
逐段检查代码:
for acc in accounts:
for i in range(1,len(acc)):
owners[acc[i]] = acc[0]
parents[acc[i]] = acc[i]
这是O(N),其中N是输入文本的大小,因为该算法只对输入的每个部分进行一次访问。请注意,每个文本元素可以具有任意大小,但是要小心,因为N是输入文本的大小。
for acc in accounts:
p = self.find(acc[1],parents) #Find parent of the first email in the list.
for i in range(2,len(acc)):
currP = self.find(acc[i],parents)
if p!=currP:
parents[currP] = p
这也是O(N),因为:
self.find(acc[i], parents)
由于路径而被视为摊销O(1)
压实。
for acc in accounts:
p = self.find(acc[1],parents)
for i in range(1,len(acc)):
merged[p].add(acc[i])
循环本身取决于N-输入文本的大小。 add()
方法在集合上操作,在python中被视为O(1)。总而言之,此块也需要O(N)。
for name,emails in merged.items():
res = [owners[name]] + sorted(emails)
results.append(res)
令人惊讶的是,这里存在瓶颈(至少在O表示法方面)。 emails
中的元素数为O(N),因为很可能是系统有一个拥有大部分电子邮件的大用户。这意味着sorted(emails)
可以取O(N log N)。
排序后创建res的代码部分:
res = [owners[name]] + <the sort result>
这仅是排序数据的线性大小,小于排序的O(N log N)。
尽管处于循环中,但排序的总成本之和不超过O(N log N)的总和,因为O(N log N)的成本假定有一个大用户。最多只能有几个大用户。
例如,假设系统中有K个相等的用户。这使每个用户的分类成本为O(N / K log(N / K))。整个系统的总和为O(N log(N / K))。如果K为常数,则变为O(N log N)。如果K是N的函数,则O(N log(N / K))小于O(N log N)。 这不是严格的证明,但足以了解为什么它是O(N log N)且不会更糟。
注意:复杂度计算有一个主要假设,即在Python中,通过长度为L的字符串键访问映射或集合是O(L)操作。通常,这是正确的,具有完善的散列函数,而Python则没有。