最近在一次采访中向朋友询问,除了简单的O(n 3 )之外,我们不知道任何解决方案。
有更好的算法吗?
问题是在整数数组中找到所有三元组,其总和小于或等于给定的总和S.
注意:我已经在SO上看到了其他类似的问题,性能为O(n 2 log n)但是所有问题都解决了这个问题的简单版本,例如arr[i] + arr[j] + arr[k] = S
或者哪里他们只是检查一个这样的三元组是否存在。
我的问题是找出i,j,k
中的所有arr[]
,arr[i] + arr[j] + arr[k] <= S
答案 0 :(得分:5)
从最坏情况渐近的角度来看,没有更好的算法,因为输出的大小可能是O(n^3)
。
e.g。让数组为数字1到n
。让S = 3n
。显然,三个数组元素的任何子集都将小于S
,并且有(n choose 3) = O(n^3)
个子集。
虽然有几种方法可以加速非最坏的情况。例如,尝试首先排序数组。那应该给你一些提示。
答案 1 :(得分:4)
我有个主意,但我不确定它是否有效。
预处理(删除元素&gt; S
)并首先对数组进行排序。
然后,在您选择arr[i]
和arr[j]
i < j
后,您可以在剩余的S - arr[i] - arr[j]
中二进制搜索array[j+1...n]
。在对索引m
进行二进制搜索后,k
可能位于j+1
和m
之间。
我认为这可能会降低复杂性。你觉得怎么样?
答案 2 :(得分:0)
这会有什么复杂性?
如果应用于排序列表(升序),f
只会逐个列出总和小于或等于s
的三元组,而不会创建重复项或扫描超过第一个元素太大了。
Haskell代码:
f [] s result = if length result == 3 then [result] else []
f (x:xs) s result
| length result == 3 = [result]
| x + sum result > s = []
| otherwise = f xs s (x:result) ++ f xs s result
输出:
*Main> length $ f [1..300] 300 []
731375
(5.09 secs, 402637784 bytes)
*Main> f [1..10] 13 []
[[3,2,1],[4,2,1],[5,2,1],[6,2,1],[7,2,1],[8,2,1],[9,2,1],[10,2,1],[4,3,1]
,[5,3,1],[6,3,1],[7,3,1],[8,3,1],[9,3,1],[5,4,1],[6,4,1],[7,4,1],[8,4,1]
,[6,5,1],[7,5,1],[4,3,2],[5,3,2],[6,3,2],[7,3,2],[8,3,2],[5,4,2],[6,4,2]
,[7,4,2],[6,5,2],[5,4,3],[6,4,3]]
答案 3 :(得分:0)
这可以用O(n ^ 2)复杂度来解决。
首先对数字进行排序 - &gt; O(n日志(n))的
现在从前面开始,一次修复一个数字。现在问题减少到在排序数组中找到2个数字,其总和是&lt; =给定总和。使用2个指针,一个从开始,一个从一开始,这可以在O(n)中求解。因此总体复杂度为O(n2)
答案 4 :(得分:0)
我在下面保留原来的答案,但实际上可以在O(n)中解决。我的新解决方案使用队列来跟踪三元组。它只返回三元组的数量,但您可以创建一个列表来轻松跟踪三元组列表,如果这是必需的。
class Queue (object):
def __init__ (self):
self.queue = []
self.itemCount = 0
def enqueue (self, item):
self.queue.append (item)
self.itemCount += 1
def dequeue (self):
self.itemCount += 1
return (self.queue.pop(0))
def findAllTriplets(li,S):
if len(li) < 3:
return "Not enough elements for triplets"
tQ = Queue() # Queue to keep track of data
tripletNum = 0 # Integer to track number of triplets to be returned
tripletSum = 0 # Value of sum of consecutive list items for tripletNum evaluation
for number in li:
# Add the number to the queue immediately and add it to the current triplet sum
tQ.enqueue(number)
tripletSum += number
# For the first 3 numbers only enqueue and add to the sum
if tQ.itemCount < 3:
continue
# Afterwards, check if the sum of the latest three is less than S
else:
if(tripletSum <= S):
tripletNum += 1
# Dequeue the oldest element in the queue and subtract it from the tracked triplet sum
tripletSum -= tQ.dequeue()
return tripletNum
我相信这个算法应该在O(N ^ 2)中完成。您应该事先对数组进行排序。
基本上,我只是找到所有可能的三元组,其中第一个索引i为零,下一个索引为j,通过搜索剩余索引(k),小于或等于x的所有总和(或者在你的情况下为S) 。之后,我将j递增1并重复该过程。一旦j到达数组的末尾,我开始过程,我的i是i + 1现在继续前进,直到我等于倒数第二个索引值(因为此时没有可能的三元组)。
Python代码
def numTriplets(a,x):
if len(a) < 3:
return None
i = 0
j = 1
triplets = []
while True:
for k in range(j+1,len(a)):
if a[i] + a[j] + a[k] <= x:
triplets.append([i,j,k])
j += 1
if j == len(a) - 1:
i += 1
j = i + 1
if i == len(a) - 2:
return triplets