[英]Finding a minimal subarray of n integers of sum >= k in linear time
最近我一直在努力解決以下問題:
給定一個整數數組,找到一個總和至少為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開始發現一些未來符合條件的段,它也會比我們從S1開始找到的段長。
其次,如果從S0到S1-1的元素之和<= 0(或等效地,如果S0之前的元素之和> = S1之前的元素之和),則可以消除S0。 這是第二個while循環所做的,其中S0是startPoints [-1]而S1是end + 1。 修剪從S0到S1-1的元素總是有意義的(對於S1或更高的終點),因為它會使段縮短而不減少其總和。
實際上,還有第三種情況我們可以消除S0:當從S0到end的距離大於到目前為止發現的最短段的長度。 我沒有實施這個案例,因為不需要它。
在這里,您可以使用偽代碼提供您正在尋找的解決方案。
curIndex = 0
while (curIndex <= endIndex)
{
if(curSum == 0)
{
startIndex = curIndex
}
curSum = curSum + curVal
curTot = curTot + 1
if(curSum >= targetVal AND curTot < minTotSofar)
{
maxSumSofar = curSum
maxStartIndex = startIndex
maxEndIndex = curIndex
minTotSofar = curTot
if(curTot == 1)
{
exit_loop
}
curSum = 0
curTot = 0
curIndex = startIndex
}
else if(curIndex == endIndex)
{
if(maxSumSofar == 0 AND curSum >= targetValue)
{
maxSumSofar = curSum
maxStartIndex = startIndex
maxEndIndex = curIndex
minTotSofar = curTot
}
else if(curSum < targetValue AND startIndex < endIndex)
{
curSum = 0
curTot = 0
curIndex = startIndex
}
}
curIndex = curIndex + 1
}
------------ JWPAT7建議后的更新
INPUTS:整數數組,索引從0到endIndex
。 與( targetVal
)進行比較的目標值(k)。
輸出:所選子集的最終添加( maxSumSoFar
),子集的起始索引( maxStartIndex
),子集的結束索引( maxEndIndex
),子集中的元素總數( minTotSofar
)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.