如何解决通过列表旋转找到最小绝对差总和的最快算法
通过从左到右旋转2个列表,如果它们是 相同 长度。
轮播示例:
List [0,1,2,3,4,5] rotated to the left = [1,5,0]
List [0,5] rotated to the right= [5,4]
绝对差之和:
List 1 = [1,4]
List 2 = [5,6,7,8]
Sum of Abs. Diff. = |1-5| + |2-6| + |3-7| + |4-8| = 16
再一次,对于任意长度的列表和整数值,任务是通过简单地向左或向右旋转两个列表来寻找最小的和。
我对旋转和获取最小绝对差之和没有问题。我只想知道更聪明的方法,因为我的算法会检查每种可能的组合,但速度很慢。
这是我的暴力手段:
list1 = [45,21,64,33,49]
list2 = [90,12,77,52,28]
choices = [] # Put all possible sums into a list to find the minimum value.
for j in range(len(list1)): # List1 does a full rotation
total = 0
for k in range(len(list1)):
total += abs(list1[k] - list2[k])
list1.append(list1.pop(0))
choices.append(total)
print(min(choices))
什么是更聪明的方法?我也希望代码更短,时间更复杂。
我设法通过应用生成器来使其更快。这个想法归功于@kuriboh!但是由于我仍然对生成器实现还不熟悉,所以只想知道这是否是实现它的最佳方法,以减少我的时间复杂性,尤其是对于我的循环。 我们还能比这种配置更快吗?
list1 = [45,28]
choices = []
l = len(list1)
for j in range(l):
total = sum([abs(int(list1[k])-int(list2[k])) for k in range(l)])
list1.append(list1.pop(0))
choices.append(total)
print(min(choices))
解决方法
由于Python会将负索引视为从右端开始计数,因此您可以将list1
的绝对值减去({list2
偏移k),其中0≤k
sum(abs(list1[i] - list2[i - k]) for i in range(len(list1)))
如果您想要所有这些值中的最小值
length = len(list1)
min(sum(abs(list1[i] - list2[i - k]) for i in range(length))
for k in range(length))
此代码仍为O(n ^ 2),但进行推送和弹出操作的次数要少得多。
我真的想不出一种比O(n ^ 2)更快的算法。
,我还没有解决完整的问题,但是在特殊情况下,输入值都是0
或1
(或任意两个不同的值,或O(1)
中的任意一个值,但是我们还需要进一步的思路才能实现),我们可以通过应用快速卷积来获得O(n log n)
时间算法。
该想法是将所有绝对差之和计算为List1 * reverse(1 - List2) + (1 - List1) * reverse(List2)
,其中1 - List
表示逐点执行该操作,而*
表示循环卷积(可在时间{{1 }}(使用一对FFT)。圆形卷积的定义是
O(n log n)
将 n-1
__
\
(f * g)(i) = /_ f(j) g((i - j) mod n).
j=0
替换为List1
,将f
替换为reverse(1 - List2)
,我们得到
g
当且仅当 n-1
__
\
(List1 * reverse(1 - List2))(i) = /_ List1(j) (1 - List2((n-1-(i-j)) mod n))
j=0
n-1
__
\
= /_ List1(j) (1 - List2((j-(i+1)) mod n)).
j=0
和List1(j) (1 - List2((j-(i+1)) mod n))
时,乘积1
为List1(j) = 1
,否则为List2((j-(i+1)) mod n) = 0
。因此,卷积的0
值计算i
具有List1
偏移1
到i+1
具有{{ 1}}。其他卷积计数与List2
个对应的0
个。鉴于我们的输入限制,这是绝对差异的总和。
代码:
0
,
带有David Eisenstat's solution的基准和我提供的两个NumPy解决方案。
500个随机整数0或1:
189.62414 ms slow_min_sum_abs_diff # Like Frank's
49.75403 ms less_slow_min_sum_abs_diff # My NumPy
10.13092 ms lesser_slow_min_sum_abs_diff # My Numpy
0.85030 ms min_sum_abs_diff # David's
0.27434 ms array_conversion # for comparison
1000个随机整数0或1:
857.02381 ms slow_min_sum_abs_diff
100.26820 ms less_slow_min_sum_abs_diff
28.55692 ms lesser_slow_min_sum_abs_diff
1.67077 ms min_sum_abs_diff
0.49301 ms array_conversion
从-10 6 到10 6 的1000个随机整数(没有戴维的整数,因为不是这样做的,会产生错误的结果):
829.18451 ms slow_min_sum_abs_diff
89.97418 ms less_slow_min_sum_abs_diff
22.69516 ms lesser_slow_min_sum_abs_diff
我不了解David的解决方案,但是他自己的验证令人信服,我自己做了一些工作,将每个较快的解决方案与下一个较慢的解决方案进行了比较(这就是为什么我编写了NumPy解决方案,因此我可以使用较大的输入):
passed: less_slow_min_sum_abs_diff (220 tests with n=100)
passed: lesser_slow_min_sum_abs_diff (89 tests with n=300)
passed: min_sum_abs_diff (146 tests with n=1000)
passed: min_sum_abs_diff (5 tests with n=10000)
在repl上完成的基准测试。它是Python 3.8.2 64位。
代码:
import numpy
from timeit import repeat,default_timer as timer
def array_conversion(a1,a2):
a1 = numpy.array(a1)
a2 = numpy.array(a2)
def convolve_circularly(a1,a2):
return numpy.round(numpy.abs(numpy.fft.ifft(numpy.fft.fft(a1) * numpy.fft.fft(a2))))
def min_sum_abs_diff(a1,a2):
a1 = numpy.array(a1)
a2 = numpy.array(a2)[::-1]
return numpy.min(convolve_circularly(a1,1 - a2) + convolve_circularly(1 - a1,a2))
def slow_min_sum_abs_diff(a1,a2):
return min(
sum(abs(a1[i] - a2[i - k]) for i in range(len(a1))) for k in range(len(a2))
)
def less_slow_min_sum_abs_diff(a1,a2):
a1 = numpy.array(a1)
a2 = numpy.array(a2)
return min(
numpy.abs(a1 - numpy.roll(a2,k)).sum()
for k in range(len(a2))
)
def lesser_slow_min_sum_abs_diff(a1,a2):
n = len(a2)
a1 = numpy.array(a1)
a2 = numpy.concatenate((a2,a2))
return min(
numpy.abs(a1 - a2[k:k+n]).sum()
for k in range(n)
)
def random_arrays(n):
a1 = numpy.random.randint(2,size=n).tolist()
a2 = numpy.random.randint(2,size=n).tolist()
return a1,a2
def verify(candidate,reference,n,timelimit=1):
t0 = timer()
count = 0
while timer() - t0 < timelimit:
a1_orig,a2_orig = random_arrays(n)
a1,a2 = a1_orig.copy(),a2_orig.copy()
expect = reference(a1,a2)
assert a1 == a1_orig and a2 == a2_orig
result = candidate(a1,a2)
assert a1 == a1_orig and a2 == a2_orig
if result != expect:
print('wrong:')
print(' expected',expect,'by',reference.__name__)
print(' got',result,candidate.__name__)
print(' a1:',a1)
print(' a2:',a2)
break
count += 1
else:
print('passed:',candidate.__name__,f'({count} tests with {n=})')
def main():
if 1:
verify(less_slow_min_sum_abs_diff,slow_min_sum_abs_diff,100)
verify(lesser_slow_min_sum_abs_diff,less_slow_min_sum_abs_diff,300)
verify(min_sum_abs_diff,lesser_slow_min_sum_abs_diff,1000)
verify(min_sum_abs_diff,10000)
print()
funcs = [
(10,slow_min_sum_abs_diff),(100,less_slow_min_sum_abs_diff),lesser_slow_min_sum_abs_diff),(1000,min_sum_abs_diff),array_conversion),]
a1,a2 = random_arrays(1000)
for _ in range(3):
for number,func in funcs:
t = min(repeat(lambda: func(a1,a2),number=number)) / number
print('%11.5f ms ' % (t * 1e3),func.__name__)
print()
if __name__ == "__main__":
main()
print('done')
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。