查找数组中所有三元组的总和小于或等于给定的总和

时间:2013-07-18 02:48:30

标签: algorithm dynamic-programming

最近在一次采访中向朋友询问,除了简单的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

5 个答案:

答案 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+1m之间。

我认为这可能会降低复杂性。你觉得怎么样?

答案 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