[英]Find longest substring in binary string with not less ones than zeros
如何在二進制字符串中找到最長的子字符串,其中余額 (即1和0的數量之差)> = 0?
例:
01110000010 - > 6:011100
1110000011110000111 - > 19:整個字符串
雖然這個問題看起來非常類似於最大值連續子序列(最大連續和)問題,但動態編程解決方案似乎並不明顯。 在分而治之的方法中,如何進行合並? 畢竟是一種“高效”的算法嗎? (一個簡單的O(n ^ 2)算法將迭代所有可能起點的所有子串。)
這是查找子字符串的修改變體,帶有一些附加條件 。 不同之處在於,在鏈接問題中,只允許這樣的子串,其中余額永遠不會低於零(在向前或向后方向上查看字符串)。 在給定的問題中,如果平衡在稍后階段恢復,則允許平衡降至零以下。
我有一個需要O(n)
額外內存和O(n)
時間的解決方案。
讓我們將索引h(i)
的“高度”表示為
h(i) = <number of 1s in the substring 1..i> - <number of 0s in the same substring>
現在可以將問題重新表述為:找到i
和j
例如h(i) <= h(j)
和ji -> max
。
顯然, h(0) = 0
,如果h(n) = 0
,那么解是整個字符串。
現在讓我們計算數組B
使B[x] = min{i: h(i) = -x
}。 換句話說,令B[x]
是最左邊的索引i
,其中h(i)= -x
。
陣列B[x]
具有至多n
的長度,並且在一個線性通道中計算。
現在我們可以迭代原始字符串,並且對於每個索引, i
計算最長序列的長度,其中非負余額以i
結尾,如下所示:
Lmax(i) = i - B[MIN{0, h(i)}]
所有i
最大的Lmax(i)
將為您提供所需的長度。
我把證明留作練習:)如果你想不通,請聯系我。
此外,我的算法需要2次傳遞原始字符串,但您可以將它們合並為一個。
這可以在O(n)
使用“高度數組”很容易地回答,表示1的數量相對於0的數量。 就像我在鏈接問題中的答案一樣。
現在,我們不再關注原始數組,而是關注兩個由高度索引的數組,一個將包含找到高度的最小索引,另一個將包含找到這樣高度的最大索引。 由於我們不想要負指數,我們可以將所有內容都移動,這樣最小高度為0。
因此對於示例案例(我在最后添加了兩個1來表明我的觀點):
1110000011010000011111 Array height visualization /\ / \ / \ \ /\/\ / \/ \ / \ / \ / \/ (lowest height = -5) Shifted height array: [5, 6, 7, 8, 7, 6, 5, 4, 3, 4, 5, 4, 5, 4, 3, 2, 1, 0, 1, 2, 3] Height: 0 1 2 3 4 5 6 7 8 first_view = [17,16,15, 8, 7, 0, 1, 2, 3] last_view = [17,18,19,20,21,22, 5, 4, 3]
請注意,我們有22個數字和23個不同的索引,0-22,表示數字之間的23個空格和填充數字
我們可以在O(n)
構建first_view
和last_view
數組。
現在,對於first_view
每個高度,我們只需要檢查last_view
每個更高的高度,並獲取與first_view
索引最大差異的索引。 例如,從高度0開始,較大高度的索引的最大值為22.因此,從索引17 + 1開始的最長子串將在索引22處結束。
要在last_view
數組中找到最大索引,可以在O(n)
中將其轉換為最大值:
last_view_max = [22,22,22,22,22,22, 5, 4, 3]
所以尋找的答案是簡單地減去first_view
從last_view_max
,
first_view = [17,16,15, 8, 7, 0, 1, 2, 3] last_view_max = [22,22,22,22,22,22, 5, 4, 3] result = [ 5, 6, 7,14,15,22, 4, 2, 0]
並且從起始索引0到結束索引22,即整個字符串,取最大值(再次在O(n)
),即22。 = d
正確性證明:
假設最大子字符串從索引i
開始,以索引j
結束。 如果索引i
處的高度與索引k<i
處的高度相同,則k..j
將是仍然滿足要求的更k..j
串。 因此,考慮每個高度的第一個指數就足夠了。 類似於最后一個指數。
我們將從頭開始尋找具有平衡零的(本地)最長子串。 我們將忽略零的字符串。 (拐角情況:全零 - >空字符串,余額永遠不會再達到零 - >整個字符串。)在這些余額為零的子字符串中,將刪除所有尾隨零。
B表示平衡> 0的子字符串,Z表示僅帶零的子字符串。 每個輸入字符串可以如下分解(偽正則表達式):
B' (ZB)* Z?
每個B都是最大可行解決方案,這意味着它不能在任何方向上擴展而不會減少平衡。 但是,如果在折疊后余額仍大於零,則可能會折疊BZB或ZBZ的序列。
注意,如果ZBZ部分具有平衡> = 0,則總是可以將BZBZB的序列折疊成單個B。(可以在線性時間內一次完成。)一旦所有這樣的序列折疊,每個ZBZ的平衡部分低於零。 盡管如此,仍有可能存在平衡度大於零的BZB部分 - 即使在平衡值低於零的BZBZB序列中,前導和尾隨BZB部分的平衡均為零。 在這一點上,似乎很難確定哪個BZB崩潰。
無論如何,通過這種簡化的數據結構,可以嘗試將所有B作為起點(如果還有余額則可能向左延伸)。 運行時間仍然是二次方,但(實際上)n小得多。
另一個經典。 應該在O(n log n)中運行,但很難實現。
最長的可行子串位於左半部分,右半部分,或者經過邊界。 為兩半調用算法。 對於邊界:
假設問題大小為n。 對於跨越邊界的最長可行子串,我們將計算子串的左半部分的平衡。
對於左半部分中-n / 2和n / 2之間的每個可能的平衡 ,確定在邊界處結束並且具有該(或更大)平衡的最長字符串的長度。 (線性時間!)對於從邊界開始的右半部分和最長的字符串執行相同的操作。 結果是兩個大小為n + 1的數組; 我們反轉其中一個,按元素添加它們並找到最大值。 (再次,線性。)
如果另一部分對此進行補償,則具有平衡> = 0且跨越邊界的子字符串可以在左側或右側部分中具有平衡<0。 (“借款”余額。)關鍵問題是借款多少; 我們迭代所有潛在的“平衡信用”並找到最佳權衡。
因為合並(查看邊界交叉串)只需要線性時間。
練習留給讀者。
靈感來自這篇博文 。 簡單高效,一次通過在線算法 ,但需要一些時間來解釋。
上面的鏈接顯示了一個不同的問題:最大子序列和。 它不能以1:1的方式映射到給定的問題,這里需要O(n)的“狀態”,與原始問題的O(1)相反。 仍然可以在O(1)中更新狀態。
讓我們重新解釋一下這個問題。 我們正在尋找輸入中最長的子串,其中平衡 ,即0
和1
之間的差值大於零。
狀態類似於我的其他分而治之的解決方案:我們計算每個位置i
和每個可能的余額 b
最長字符串的起始位置s(i, b)
,其中余額為b
或更大, 結束於位置i
。 也就是說,從索引s(i, b) + 1
並在i
結束的字符串具有余額b
或更大,並且不再有這樣的字符串在i
處結束。 我們通過最大化i - s(i, 0)
找到結果。
當然,我們不會將所有s(i, b)
保留在內存中,只保留當前i
(我們迭代輸入)的內存。 我們從s(0, b) := 0
表示b <= 0
且:= undefined
表示b > 0
。 對於每個i
,我們使用以下規則進行更新:
1
: s(i, b) := s(i - 1, b - 1)
。 0
: s(i, b) := s(i - 1, b + 1)
如果定義, s(i, 0) := i
如果s(i - 1, 1)
未定義。 函數s
(對於當前i
)可以實現為指向長度2n + 1
的數組的指針; 根據輸入,此指針向前或向后移動。 在每次迭代中,我們都注意到s(i, 0)
。
如果從開始到i
的平衡為負,則狀態函數s
變得有效。 它記錄了達到零平衡的最早起始點,對於尚未讀取的所有可能的1
秒數。
因為狀態函數的遞歸定義等同於其直接定義 - 最長字符串的起始位置,其余為b
或更大,以位置i
結束。
通過歸納證明。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.