【问题标题】:Find all triplets in array with sum less than or equal to given sum查找数组中总和小于或等于给定总和的所有三元组
【发布时间】:2013-07-16 19:49:31
【问题描述】:

这是最近在一次采访中向一位朋友提出的问题,我们不知道除了简单的 O(n3) 解决方案之外的任何解决方案。

有更好的算法吗?

问题是在一个整数数组中找到所有三元组,其总和小于或等于给定总和 S。

注意:我在 SO 上看到了其他性能为 O(n2log n) 的此类问题,但他们都在解决这个问题的更简单版本,例如 arr[i] + arr[j] + arr[k] = S 或它们所在的位置只检查是否存在一个这样的三元组。

我的问题是找出arr[] 中的所有i,j,k 使得arr[i] + arr[j] + arr[k] <= S

【问题讨论】:

标签: algorithm dynamic-programming


【解决方案1】:

这可以在 O(n2) 复杂度内解决。

首先对数字进行排序 → O(nlog(n))

现在从前面开始,一次固定一个数字。现在问题减少到在排序数组中找到 2 个数字,其总和 2)

【讨论】:

    【解决方案2】:

    从最坏情况的渐近角度来看,没有更好的算法,因为输出的大小可能是 O(n3)。

    例如让数组是数字 1 到 n。让S = 3n。显然,三个数组元素的任何子集都将小于S,并且存在(n choose 3) = O(n³) 子集。

    不过,有几种方法可以加快非最坏情况的速度。例如,先尝试对数组进行排序。这应该会给你一些提示。

    【讨论】:

      【解决方案3】:

      我将原始答案保留在下面,但这实际上可以在 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(N2) 中解决问题。不过,您应该事先对数组进行排序。

      本质上,我只是通过在剩余索引 (k) 中搜索小于或等于 x(或您的情况下为 S)的所有总和来找到所有可能的三元组,其中第一个索引 i 为零,下一个索引为 j .之后,我将 j 增加 1 并重复该过程。一旦 j 到达数组的末尾,我就开始这个过程,我的 i 现在是 i + 1 并继续进行,直到 i 等于倒数第二个索引值(因为那时没有可能的三元组了)。

      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
      

      【讨论】:

        【解决方案4】:

        这会有什么复杂性?

        如果应用于排序列表(升序),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]]
        

        【讨论】:

          【解决方案5】:

          我有一个想法,但我不确定它是否有效。

          预处理(删除元素> S)并首先对数组进行排序。

          然后,在您选择arr[i]arr[j] 其中i &lt; j 之后,您可以在剩余的array[j+1...n] 中对S - arr[i] - arr[j] 进行二分搜索。对索引m 进行二进制搜索后,k 可能位于j+1m 之间。

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

          【讨论】:

          • 是的,我认为这会将复杂度降低到 n2.log(n)
          • 方法可以改进一点,在对 A 进行排序和过滤后,您将拥有长度为 n 的 B 数组。然后你必须遍历数组B[i] 的元素并检测适合下一个公式的窗口B[j..k]: ... j = 1; if (B[i]+B[j]
          • @user486075 我正在尽力理解你的话。你的意思是当arr[i] + arr[j] &gt; S 成立时,我们根本不需要对k 进行二分搜索?
          • 你是对的,如果是负数可能是错误的。
          • +1 用于引入 3 个有用的步骤 - 减少问题(删除 >S 个元素)、排序和引入对第 3 个元素的二分搜索。
          猜你喜欢
          • 1970-01-01
          • 2022-10-12
          • 1970-01-01
          • 2015-03-19
          • 1970-01-01
          • 1970-01-01
          • 2014-05-26
          • 2020-04-06
          • 1970-01-01
          相关资源
          最近更新 更多