我有一个有趣的问题,给出了两个排序的数组:
a有n个元素,b有n-1个元素。
b包含除了一个元素之外的所有元素。
如何在O(log n)时间内找到该元素?
我试过这段代码:
def lostElements2(a, b):
if len(a)<len(b):
a, b = b, a
l, r = 0, len(a)-1
while l<r:
m = l + (r-l)//2
if a[m]==b[m]:
l = m+1
else:
r = m - 1
return a[r]
print(lostElements2([-1,0,4,5,7,9], [-1,0,4,5,9]))
我不知道我应该在函数中返回什么,如果它是[l],[r]?
我得知函数内部的逻辑应该如何:如果两个数组的中间值匹配,则意味着,b直到中点与a相同,因此缺少的元素必须在mid的右边
但我无法创建最终解决方案,何时应该停止循环以及应该返回什么?它如何保证[l]或[r]确实是缺失的元素?
答案 0 :(得分:4)
这个问题的原理很简单,细节很难。
您已安排数组a
是较长的数组。好,这简化了生活。现在,您需要在a
的值与a
的值不同的第一个位置返回b
的值。
现在您需要确保处理以下边缘情况。
a
具有值的位置。a = [1, 1, 2, 2, 2, 2, 3]
而b = [1, 2, 2, 2, 2, 3]
- 当你落在中间时,价值相匹配的事实会误导你!答案 1 :(得分:4)
l
和r
应该是l
始终是列表相等的位置,而r
始终是它们不同的位置。 IE浏览器。
a[l]==b[l]
和a[r]!=b[r]
代码中唯一的错误是将r
更新为m-1
而不是m
。如果我们知道a[m]!=b[m]
,我们就可以安全地设置r=m
。但是将其设置为m-1
会冒a[r]==b[r]
,这会破坏算法。
def lostElements2(a, b):
if len(a) < len(b):
a, b = b, a
if a[0] != b[0]:
return a[0]
l, r = 0, len(a)-1
while l < r:
m = l + (r-l)//2
if a[m] == b[m]:
l = m+1
else:
r = m # The only change
return a[r]
(正如@btilly所指出的,如果我们允许重复的值,这个算法会失败。)
从@btilly编辑
要修复这个潜在的缺陷,如果值相等,我们会搜索具有相同值的范围。为此,我们以1号,2号,4号,8号等步长向前走,直到值切换,然后进行二分查找。并向后走同样的道路。现在寻找每个边缘的差异。
该搜索所需的工作是O(log(k))
,其中k
是重复值的长度。所以我们现在用搜索替换O(log(n))
查找。如果在搜索的长度上存在上限K
,则会产生总体运行时间。 O(log(n)log(K))
。这使得最坏情况下的运行时间为O(log(n)^2)
。如果K
接近sqrt(n)
,则很容易实际遇到最糟糕的情况。
我在评论中声明,如果最多K
个元素的重复次数超过K
次,则运行时间为O(log(n)log(K))
。进一步分析,这种说法是错误的。如果K = log(n)
和log(n)
长度为sqrt(n)
的游戏符合搜索的所有选项,那么您的运行时间为O(log(n)^2)
,而不是O(log(n)log(log(n)))
。
但是,如果最多log(K)
个元素的重复次数超过K
次,那么您的运行时间将为O(log(n)log(K))
。对于大多数情况来说,哪个应该足够好。 : - )
答案 2 :(得分:3)
您的代码不处理缺少元素是索引m本身的情况。后面的if / else子句将始终移动缺少元素的边界,使其不包括m。
您可以通过添加额外的支票来解决此问题:
if a[m]==b[m]:
l = m+1
elif m==0 or a[m-1]==b[m-1]:
return a[m]
else:
r = m - 1
另一种方法是存储m的最后一个值:
last_m = 0
...
else:
last_m = m
r = m - 1
...
return a[last_m]
这会导致它在上次检测到不匹配时返回。