这是线性时间,也是线性空间。额外的空间来自一个可以增长到线性大小的deque。(还有第二个数组来维护累计和,但可以很容易地删除它。)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)
deque从左到右拥有一系列候选起始位置。关键不变的是deque中的位置也按“sumbore”的值递增排序。在
要了解原因,请考虑x>;y的两个位置x和y,并假设sumbore[x]<;=sumbore[y]。那么x是一个比y更好的开始位置(对于以x或更晚结束的段),所以我们再也不用考虑y了。在
进一步说明:
想象一下一个看起来像这样的天真算法:
^{pr2}$
我试图改进内部循环,只考虑某些起始点,而不是所有可能的起点。那么,我们什么时候可以从进一步考虑中排除某个特定的起点呢?有两种情况。考虑两个起点S0和S1,其中S0位于S1的左侧。在
首先,我们可以消除S0,如果我们发现S1开始了一个合格的段(也就是说,一个段的总和至少为k)。这就是第一个while循环的作用,其中start是S0,startPoints[0]是S1。即使我们找到了一些从S0开始的符合条件的段,它也会比我们已经找到的从S1开始的段长。在
第二,如果从S0到S1-1的元素之和为<;=0(或者,如果S0之前的元素之和=S1之前的元素之和,则等价地)我们可以消除S0。这是第二个while循环的作用,其中S0是startPoints[-1],S1是end+1。将元素从S0修剪到S1-1总是有意义的(对于S1或更高的端点),因为这样可以使线段变短而不减少其和。在
实际上,还有第三种情况,我们可以消除S0:当S0到终点的距离大于到目前为止找到的最短线段的长度。我没有实施这个案例,因为它不需要。在