找到具有足够平均分数的最长序列

时间:2016-11-11 23:52:32

标签: algorithm

我有一长串的0到1之间的分数。如何有效地找到比x元素更长的所有连续子列表,使每个子列表中的平均分数不低于y?

例如,如何查找超过300个元素的所有连续子列表,以使这些子列表的平均得分不低于0.8?

我主要对满足这些标准的最长的子列表感兴趣,而不是所有的子列表。所以我正在寻找所有最长的子列表。

2 个答案:

答案 0 :(得分:1)

我认为一般解决方案总是O(N^2)。我将演示Python中的代码以及您可以实现的一些优化,以将性能提高几个数量级。

让我们生成一些数据:

from random import random
scores_list = [random() for i in range(10000)]
scores_len = len(scores_list)

让我们说这些是我们的目标值:

# Your average
avg = 0.55
# Your min lenght
min_len = 10

这是一个天真的暴力解决方案

res = []
for i in range(scores_len - min_len):
  for j in range(i+min_len, scores_len):
    l = scores_list[i:j]
    if sum(l) / (j - i) >= avg:
      res.append(l)

由于必须执行10000^210^8)操作,因此运行速度非常慢。

以下是我们如何做得更好。它仍然是二次的,但有一些技巧可以让它的执行速度更快:

res = []
i = 0
while i < scores_len - min_len:
  j = i + min_len
  di = scores_len
  dj = 0
  current_sum = sum(scores_list[i:j])
  while j < scores_len:
    current_sum += sum(scores_list[j-dj:j])
    current_avg = current_sum/(j - i)
    if current_avg >= avg:
      res.append(scores_list[i:j])
      dj = 1
      di = 1
    else:
      dj = max(1, int((avg * (j - i) - current_sum)/(1 - avg)))
      di = min(di, max(1, int(((j-i) * avg - current_sum)/avg)))
    j += dj
  i += di

对于均匀分布(我们在这里)并且对于给定的目标值,它将仅执行少于10^6次操作(~7 * 10^5),这比蛮力解决方案低两个数量级。

所以基本上如果你有一些目标子列表,它将表现得非常好。如果你有很多这样的算法,那么这个算法就像蛮力算法一样。

答案 1 :(得分:1)

如果您只想要最长这样的子串,可以通过略微转换问题然后二进制搜索超过最大值来解决 O(n log n)时间解决方案长度。

让分数的输入列表为x [1],...,x [n]。让我们通过从每个元素中减去y来变换该列表,以形成列表z [1],...,z [n],其元素可以是正数或负数。请注意,当且仅当z中相应子列表中元素的时,任何子列表x [i ... j]的平均得分至少为y(即z [i] + z [i + 1] + ... + z [j])至少为0.所以,如果我们有办法有效地计算z []中任何子列表的最大和T(扰流板:我们做),作为副作用,这将告诉我们x []中是否有任何子列表,其平均得分至少为y:如果T> = 0,那么至少有一个这样的子列表,如果T&lt; 0然后在x [](甚至不是单元素子列表)中有 no 子列表,其平均得分至少为y。但是,这还没有提供我们回答原始问题所需的所有信息,因为没有什么能迫使z中的最大和子列表具有最大长度:它可能会更长子列表存在总体平均值较低,但平均值至少为y。

这可以通过概括找到具有最大总和的子列表的问题来解决:我们现在要求在所有具有至少长度的子列表中具有最大总和的子列表,而不是要求具有最大总和的子列表。一些给定的k 。我现在描述一种算法,给定一个数字列表z [1],...,z [n],每个都可以是正数或负数,任何正整数k,将计算最大总和任何具有长度至少 k的z []的子列表,以及实现该总和的特定子列表的位置,并且具有该总和的所有子列表中具有最长可能的长度。它是Kadane's algorithm的轻微概括。

FindMaxSumLongerThan(z[], k):
    v = 0                 # Sum of the rightmost k numbers in the current sublist
    For i from 1 to k:
        v = v + z[i]

    best = v
    bestStart = 1
    bestEnd = k

    # Now for each i, with k+1 <= i <= n, find the biggest sum ending at position i.
    tail = -1          # Will contain the maximum sum among all lists ending at i-k
    tailLen = 0        # The length of the longest list having the above sum
    For i from k+1 to n:
        If tail >= 0:
            tail = tail + z[i-k]
            tailLen = tailLen + 1
        Else:
            tail = z[i-k]
            tailLen = 1

        If tail >= 0:
            nonnegTail = tail
            nonnegTailLen = tailLen
        Else:
            nonnegTail = 0
            nonnegTailLen = 0

        v = v + z[i] - z[i-k]    # Slide the window right 1 position
        If v + nonnegTail > best:
            best = v + nonnegTail
            bestStart = i - k - nonnegTailLen + 1
            bestEnd = i

上述算法需要O(n)时间和O(1)空间,返回best中的最大总和以及在bestStart和{bestEnd中实现该总和的某个子列表的开始和结束位置分别为{1}}。

以上有用吗?对于给定的输入列表x [],假设我们首先通过从每个元素中减去y来将x []变换为z [],如上所述;这将是每次调用FindMaxSumLongerThan()时传递的z []。我们可以通过调用z []函数和给定的最小子列表长度k作为k:best(k)的数学函数来查看best的值。由于FindMaxSumLongerThan()找到长度至少 k的z []的任何子列表的最大和,因此best(k)是k的非递增函数。 (假设我们设置k = 5并发现任何子列表的最大总和为42;如果我们再次尝试k = 4或k = 3,我们保证总共至少找到42。)这意味着我们可以在k 上进行二分搜索,找到最大的k,使得best(k)&gt; = 0:那么k将是x []的最长的子列表,其平均值为至少y。生成的bestStartbestEnd将标识具有此属性的特定子列表;在不增加时间复杂度的情况下,很容易修改算法以找到这些子列表中的所有(最多n个 - 每个最右侧位置)。