python - 在线性时间内找到总和 >= k 的 n 个整数的最小子数组

标签 python algorithm

最近我一直在努力解决以下问题:

给定一个整数数组,找到一个总和至少为 k 的最小(最短长度)子数组。

显然这可以在 O(n^2) 中轻松完成。我能够编写一个算法,在线性时间内解决自然数,但我无法计算出整数。

我最近的尝试是这样的:

def find_minimal_length_subarr_z(arr, min_sum):
    found = False
    start = end = cur_end = cur_sum = 0
    for cur_start in range(len(arr)):
        if cur_end <= cur_start:
            cur_end, cur_sum = cur_start, arr[cur_start]
        else:
            cur_sum -= arr[cur_start-1]
        # Expand
        while cur_sum < min_sum and cur_end < len(arr)-1:
            cur_end += 1
            cur_sum += arr[cur_end]
        # Contract
        while cur_end > cur_start:
            new_sum = cur_sum - arr[cur_end]
            if new_sum >= min_sum or new_sum >= cur_sum:
                cur_end -= 1
                cur_sum = new_sum
            else:
                break
        if cur_sum >= min_sum and (not found or cur_end-cur_start < end-start):
            start, end, found = cur_start, cur_end, True
    if found:
        return start, end

例如:

[8, -7, 5, 5, 4], 12 => (2, 4)

但是,它失败了:

[-12, 2, 2, -12, 2, 0], 4

正确的结果是 (1, 2) 但算法没有找到它。

这完全可以在线性时间内完成(最好具有恒定的空间复杂度)吗?

最佳答案

这是一个线性时间,也是线性空间。额外的空间来自可以增长到线性大小的双端队列。 (还有第二个数组来维护累积和,但可以很容易地删除它。)

from collections import deque
def find_minimal_length_subarr(arr, k):
   # assume k is positive
   sumBefore = [0]
   for x in arr: sumBefore.append(sumBefore[-1] + x)
   bestStart = -1
   bestEnd = len(arr)
   startPoints = deque()
   start = 0
   for end in range(len(arr)):
      totalToEnd = sumBefore[end+1]
      while startPoints and totalToEnd - sumBefore[startPoints[0]] >= k: # adjust start
         start = startPoints.popleft()
      if totalToEnd - sumBefore[start] >= k and end-start < bestEnd-bestStart:
         bestStart,bestEnd = start,end
      while startPoints and totalToEnd <= sumBefore[startPoints[-1]]: # remove bad candidates
         startPoints.pop()
      startPoints.append(end+1) # end+1 is a new candidate
   return (bestStart,bestEnd)

双端队列从左到右保存一系列候选起始位置。关键不变量是双端队列中的位置也按“sumBefore”的递增值排序。

要了解原因,请考虑 x > y 的两个位置 x 和 y,并假设 sumBefore[x] <= sumBefore[y]。那么 x 是一个严格来说比 y 更好的开始位置(对于以 x 或更晚结束的段),所以我们再也不需要考虑 y 了。

进一步说明:

想象一个看起来像这样的朴素算法:

for end in 0..N-1
   for start in 0..end
      check the segment from start to end

我尝试改进内部循环以仅考虑某些起点而不是所有可能的起点。那么我们什么时候可以从进一步的考虑中排除一个特定的起点呢?在两种情况下。考虑两个起点 S0 和 S1,S0 在 S1 的左边。

首先,如果我们发现 S1 开始了一个符合条件的段(即,一个总和至少为 k 的段),我们就可以消除 S0。这就是第一个 while 循环所做的,其中 start 是 S0,startPoints[0] 是 S1。即使我们找到了一些从 S0 开始的 future 符合条件的段,它也会比我们已经找到的从 S1 开始的段长。

其次,如果从 S0 到 S1-1 的元素之和 <= 0(或者,等效地,如果 S0 之前的元素之和 >= S1 之前的元素之和),我们可以消除 S0。这就是第二个 while 循环所做的,其中 S0 是 startPoints[-1],S1 是 end+1。修剪从 S0 到 S1-1 的元素总是有意义的(对于 S1 或更晚的端点),因为它使段更短而不减少其总和。

实际上,还有第三种情况我们可以消除 S0:当 S0 到末端的距离大于目前找到的最短线段的长度时。我没有实现这个案例,因为它不需要。

关于python - 在线性时间内找到总和 >= k 的 n 个整数的最小子数组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17391025/

相关文章:

algorithm - 凸包或给定点集的两个凸包具有尽可能低的周长

algorithm - 使第一个元素大于最后一个元素的最大子数组的长度

python - C 或 Python 中的快速最大二分匹配

python - urllib2.URL错误: <urlopen error unknown url type: c>

python - 嵌套字典的查找时间如何增加?

Python 检查数组中元素的值

python - 使用 python-opencv 的 crontab 摄像头捕获

algorithm - 在无向图上查找所有路径

algorithm - 运行时分析中的概率?

python - Django:以后有什么方法可以获取上传文件的日期和名称?