给出两个整数列表,找到彼此距离内的每一对< O(N ^ 2)

时间:2012-11-29 14:15:05

标签: python algorithm

我有两个排序的整数列表。我想分别从第一个和第二个列表中找到彼此相距一定距离的所有整数对。

天真的方法是检查每对,导致O(N ^ 2)时间。我确信有一种方法可以在O(N * logN)或更短的时间内完成。

在python中,天真的O(N ^ 2)方法如下:

def find_items_within(list1, list2, within):
    for l1 in list1:
        for l2 in list2:
            if abs(l1 - l2) <= within:
                yield (l1, l2)

pythonic答案的额外分数。

应用说明

我只想指出这个小谜题的目的。我正在搜索文档,并希望在另一个术语的特定距离内找到一个术语的所有出现。首先,您可以找到两个术语的术语向量,然后您可以使用下面描述的算法来确定它们是否在彼此的给定距离内。

7 个答案:

答案 0 :(得分:7)

没有办法比O(n^2)做得更好,因为有O(n^2)对,而对于within = infinity,你需要产生所有这些对。


要查找这些对的数字是一个不同的故事,可以通过查找满足e的每个元素within-e < arr[idx]的第一个索引来完成。例如,使用二进制搜索可以有效地找到索引idx - 这将使O(nlogn)解决方案找到这些对的数字

它也可以在线性时间(O(n))内完成,因为在找到第一个[a,b]范围后,您并不需要对所有元素进行二元搜索,请注意彼此范围[a',b'] - 如果a>a'然后b>=b' - 那么您实际上需要使用两个指针迭代列表并“永不回头”以获得线性时间复杂度。

伪代码:(对于线性时间解决方案)

numPairs <- 0
i <- 0
a <- 0
b <- 0
while (i < list1.length):
  while (a < i && list1[i] - list2[a] > within):
      a <- a+1
  while (b < list2.length && list2[b] - list1[i] < within):
      b <- b+1
  if (b > a):
      numPairs <- numPairs + (b-a)
  i <- i+1
return numPairs

(我从初始的伪代码中做了一些修改 - 因为第一个伪目标是在一个列表中查找范围内的对数 - 而不是两个列表之间的匹配,对不起)

答案 1 :(得分:6)

此代码为O(n * log(n)+ m),其中m是答案的大小。

def find_items_within(l1, l2, dist):
    l1.sort()
    l2.sort()
    b = 0
    e = 0
    ans = []
    for a in l1:
        while b < len(l2) and a - l2[b] > dist:
            b += 1
        while e < len(l2) and l2[e] - a <= dist:
            e += 1
        ans.extend([(a,x) for x in l2[b:e]])
    return ans

在最坏的情况下,有可能m = n*n,但如果答案只是所有可能对的一小部分,那么速度要快得多。

答案 2 :(得分:2)

这里的内容与您提供的界面相同:

def find_items_within(list1, list2, within):
    i2_idx = 0
    shared = []
    for i1 in list1:
        # pop values to small
        while shared and abs(shared[0] - i1) > within: 
            shared.pop(0)
        # insert new values 
        while i2_idx < len(list2) and abs(list2[i2_idx] - i1) <= within:
            shared.append(list2[i2_idx])
            i2_idx += 1
        # return result
        for result in zip([i1] * len(shared), shared):
            yield result

for item in find_items_within([1,2,3,4,5,6], [3,4,5,6,7], 2):
    print item

不是很漂亮,但它应该在O(N*M)中处理,其中N是list1的长度,M是每个项目的共享对列表(假设元素丢弃和附加到shared的内容平均不变。

答案 3 :(得分:0)

这似乎有效:

from itertools import takewhile
def myslice(lst,start,stop,stride=1):
    stop = len(lst) if stop is None else stop
    for i in xrange(start,stop,stride):
        yield lst[i]

def find_items_within(lst1,lst2,within):
    l2_start = 0
    for l1 in lst1:
        try:
            l2_start,l2 = next( (i,x) for i,x in enumerate(myslice(lst2,l2_start,None),l2_start) if abs(l1-x) <= within )
            yield l1,l2
            for l2 in takewhile(lambda x:(abs(l1-x) <= within), myslice(lst2,l2_start+1,None)):
                yield l1,l2
        except StopIteration:
            pass


x = range(10)
y = range(10)
print list(find_items_within(x,y,2.5))

答案 4 :(得分:0)

根据列表中值的分布,您可以通过使用分箱来加快速度:获取所有值下降的范围(min(A+B), max(A+B)),并将该范围划分为与您正在考虑的距离D大小相同的区间。然后,要查找所有对,您只需要比较bin中或相邻bin中的值。如果您的值在许多箱之间分开,这是避免进行M * N比较的简单方法。

另一种在实践中可能同样容易的技术:做一种有界线性扫描。从头开始,将索引保持在列表A和列表B中。在每次迭代时,将索引前进到列表A(从第一个元素开始),调用此元素A0。然后,将索引推进到列表B中。记住B的最后一个值小于A0-D(这是我们要在下一次迭代时启动的地方)。但是当你在A0-D和A0 + D之间找到值时继续前进 - 这些是你正在寻找的对。一旦B中的值变得大于A0 + D,停止该迭代并开始下一个 - 将一个元素进一步前进到A中,并从B的最后一个位置开始扫描B&lt; A0-d。

如果你平均每个元素有一个恒定数量的附近对,我认为这应该是O(M + N)?

答案 5 :(得分:0)

此方法使用的字典的键值可能为list2,其值为list1的值列表,距离list2的值。

def find_items_within(list1, list2, within):
    a = {}
    for l1 in list1:
        for i in range(l1-within, l1+within+1):
            if i not in a:
                a[i] = []
            a[i].append(l1)
    for l2 in list2:
        if l2 in a:
            for l1 in a[l2]:
                yield(l1, l2)

这个的复杂性有点愚蠢。对于大小为M的列表1和大小为N的列表2以及大小为W的列表2,它是O(log(M * W)*(M * W + N))。在实践中,我认为它适用于小W。

奖金:这也适用于未排序的列表。

答案 6 :(得分:0)

您可以在线性时间list2中找到来自[x - within, x + within]区间的x区间内所有list1来自O(n)的整数})使用“扫描线”技术(参见How to Find All Overlapping IntervalsSub O(n^2) algorithm for counting nested intervals?)。

要从list1枚举相应的时间间隔,您需要O(m)时间m是间隔数,即整体算法为O(n*m)

from collections import namedtuple
from heapq import merge

def find_items_within(list1, list2, within):
    issorted = lambda L: all(x <= y for x, y in zip(L, L[1:]))
    assert issorted(list1) and issorted(list2) and within >= 0

    # get sorted endpoints - O(n) (due to list1, list2 are sorted)
    Event = namedtuple('Event', "endpoint x type")
    def get_events(lst, delta, type):
        return (Event(x + delta, x, type) for x in lst)
    START, POINT, END = 0, 1, 2 
    events = merge(get_events(list1, delta=-within, type=START),
                   get_events(list1, delta=within, type=END),
                   get_events(list2, delta=0, type=POINT))

    # O(n * m), m - number of points in `list1` that are 
    #               within distance from given point in `list2`
    started = set() # started intervals
    for e in events:  # O(n)
        if e.type is START: # started interval
            started.add(e.x) # O(m) is worst case (O(1) amortized)
        elif e.type is END: # ended interval
            started.remove(e.x)  # O(m) is worst case (O(1) amortized)
        else:  # found point
            assert e.type is POINT
            for x in started:  # O(m)
                yield x, e.x

允许list1中的重复值;您可以为x中的每个Event添加索引,并使用字典index -> x代替started集。