问题描述
假设我们有数据a₁,...,aₙ,其中n是偶数整数,每个aᵢ∈ℝ。还定义两对元素之间的距离dis(aᵢ,aⱼ)= | aᵢ-aⱼ|。现在,程序应输出按距离从小到大的成对元素列表。程序还应该将输入数据打包成对,因此每个元素aᵢ在输出中只会出现一次。
例如,给定输入[1,0.4,3,1.1],输出应为[(1,1.1),(0.4,3)]。
幼稚的暴力破解方法是计算所有C(n,2)对,并对每对的距离进行排序。
def not_in_list_of_pair(i,ls):
return not i in [p[0] for p in ls] + [p[1] for p in ls]
def calc(ls):
ls = sorted(ls)
d ={}
for idx1,i in enumerate(ls[:-1]):
for idx2,j in enumerate(ls[idx1+1:],idx1 + 1):
d[(i,j)] = j - i
# 2nd part
res = []
for pair in sorted(d,key = lambda k: d[k]):
i,j = pair
if not_in_list_of_pair(i,res) and not_in_list_of_pair(j,res):
res.append(pair)
return res
# another example
ls = [1,0.1,2,2.4,3,4,1.5]
assert calc(ls) == [(2,2.4),(1,1.5),(3,4)]
但是这种幼稚的方法仅适用于O(n²),第二部分(提取最小距离)也很慢。因此,我正在寻找一种更有效的方法来解决此问题。谢谢!
解决方法
我不得不说,您对问题的描述不明确,说明中的复杂性也不正确,即您必须计算所有整数对的距离(即O(n ^ 2))然后对所有距离进行排序(即O(n ^ 2 * log(n ^ 2)))。
对于这个问题,您基本上是在寻找距离最小的两个整数,挑选出这两个整数,并对其余整数重复相同的过程。
一个幼稚的解决方案是,假设整数是 sorted ,而我们只找到一对距离最小的整数,那么我们只需计算每个距离两个相邻整数(例如ls[0]
和ls[1]
之间的距离,ls[1]
和ls[2]
之间的距离,...,ls[n - 2]
之间的距离和ls[n - 1]
),找出哪对最小。找到一个整数后,删除两个选定的整数,其余整数仍会排序。如果我们想找到距离最小的下一对整数,则问题仍然相同。
在两个方面,幼稚的解决方案仍然很昂贵:(1)我们需要每次计算两个相邻整数的距离; (2)我们需要从已排序的数组中删除两个整数,并保持该数组已排序。
实际上,要解决(1),我们不必每次都计算所有距离。例如,假设我们有6个整数,我们在其中计算了dist(0,1),dist(1、2),dist(2、3),dist(3、4),dist(4、5)。我们发现第二和第三整数是壁橱的整数,因此我们输出并删除了第二和第三整数。对于下一轮,我们需要计算dist(0,1),dist(1,4),dist(4,5)。我们可以看到我们只需要删除dist(1,2)和dist(3,4)即可,因为它们没有用,但是我们需要在dist(0,1)和dist(0,1)和dist(4,5)不变。我们可以维护一个btree来达到目的。
要解决(2),可以从中间删除项目的最佳数据结构是复杂度为O(1)的双链表。但是我们现在正在使用数组,我们可能不想将数组更改为链表。一种方法是,我们使用索引数组来模仿双向链接列表。
这里是一个例子。
更新1:我发现OrderedDict
不会每次都弹出最小项目。我在python中找不到任何可用作btree的数据结构。我必须使用一个堆,在其中不能删除那些无用的距离,但可以识别并忽略它们。对不起,这个错误。
更新2:在else
循环中添加一个while
分支,即,当我们看到无用的项目时,我们不应该更改双链表。
更新3:只要意识到在while循环中的每次迭代中,堆中的项目将不超过n
个。因此复杂度大约为O(n log n),其中n为整数。
from heapq import *
def calc(ls):
ls = sorted(ls) # O(nlogn)
n = len(ls)
# mimic a double linked list
left = [i - 1 for i in range(n)]
right = [i + 1 for i in range(n)]
appeared = [False for i in range(n)]
btree = []
for i in range(0,n - 1):
# distance of adjacent integers,and their indices
heappush(btree,(ls[i + 1] - ls[i],i,i + 1))
# roughly O(n log n),because the heap will have at most `n` items in each iteration
result = []
while len(btree) != 0:
minimal = heappop(btree)
a,b = minimal[1:3]
# skip if either a or b appeared
if not appeared[a] and not appeared[b]:
result.append((ls[a],ls[b]))
appeared[a] = True
appeared[b] = True
else:
continue # this is important
#print result
if left[a] != -1:
right[left[a]] = right[b]
if right[b] != n:
left[right[b]] = left[a]
if left[a] != -1 and right[b] != n:
heappush(btree,(ls[right[b]] - ls[left[a]],left[a],right[b]))
return result
ls = [1,0.1,2,2.4,3,4,1.5]
print calc(ls)
具有以下输出:
[(2,2.4),(1,1.5),(3,4)]
Note: The number of input integers is 7,which is NOT even.
我对Python不太熟悉,因此上面的代码片段中可能没有使用最好的数据结构。