这是关于分析流行面试问题解决方案的复杂性。
问题
有一个函数concat(str1, str2)
连接两个字符串。函数的成本由两个输入字符串len(str1) + len(str2)
的长度来衡量。实现仅使用concat_all(strs)
函数连接字符串列表的concat(str1, str2)
。目标是最小化总的连续成本。
警告
通常在实践中,你会非常谨慎地在循环中连接字符串对。可以找到一些很好的解释here和here。实际上,我目睹了由此类代码引起的严重性事故。除了警告,让我们说这是一个面试问题。对我来说真正感兴趣的是围绕各种解决方案的复杂性分析。
如果您想考虑问题,可以暂停。我将在下面透露一些解决方案。
解决方案
def concat_all(strs):
result = ''
for str in strs:
result = concat(result, str)
return result
def concat_all(strs):
heap = MinHeap(strs, key_func=len)
while len(heap) > 1:
str1 = heap.pop()
str2 = heap.pop()
heap.push(concat(str1, str2))
return heap.pop()
def concat_all(strs):
if len(strs) == 1:
return strs[0]
if len(strs) == 2:
return concat(strs[0], strs[1])
mid = len(strs) // 2
str1 = concat_all(strs[:mid])
str2 = concat_all(strs[mid:])
return concat(str1, str2)
复杂性
我真正在努力并且在这里问的是上面使用最小堆的第二种方法的复杂性。
我们假设列表中的字符串数为n
,所有字符串中的字符总数为m
。天真解决方案的上限是O(mn)
。 binary-concat具有theta(mlog(n))
的精确边界。这是对我来说难以捉摸的最小堆方法。
我猜它有O(mlog(n) + nlog(n))
的上限。第二个术语nlog(n)
与维护堆有关;有n
个concats,每个concat都在log(n)
更新堆。如果我们只关注连接的成本并忽略维护最小堆的成本,那么min-heap方法的总体复杂性可以降低到O(mlog(n))
。然后min-heap是一种比二元连接更优化的方法,因为前者mlog(n)
是上限,而后者则是精确界限。
但我似乎无法证明这一点,甚至找不到支持猜测上限的良好直觉。上限是否可以低于O(mlog(n))
?
答案 0 :(得分:1)
对于天真的解决方案,显然最坏的情况出现了
m1
几乎等于m,并且你指出了O(nm)
复杂度。
对于最小堆,最坏情况有点不同,它包含任何字符串的相同长度。在这种情况下,它的工作方式与二进制concat的情况完全相同,但您还必须维护最小堆结构。所以是的,它会比现实生活中的案例3贵一点。然而,从复杂的角度来看,两者都在O(m log n)
,因为我们m > n
和O(m log n + n log n)
可以缩减为O(m log n)
。
为了更严格地证明最小堆复杂性,我们可以证明当我们在一组k个字符串中取两个最小的字符串,并用S
表示两个最小字符串的总和时,我们就有了:(m-S)/(k-1) >= S/2
(它只是意味着两个最小字符串的平均值小于k-2
其他字符串的平均值)。重新制定导致S <= 2m/(k+1)
。让我们将它应用于最小堆算法:
因此,最小堆的计算时间为2m*[1/(n+1) + 1/n + ... + 1/2 + 1]
<{1}}