乙個序列中,滿足某些條件的,子串的,最長/最短/個數。
時間複雜度分析:暴力解法的時間複雜度一般為o(n ^ 3):o(n ^ 2)列舉所有的子串,o(n)判斷是否滿足條件。
滑動視窗在兩個方面都降低時間複雜度:首先並不列舉所有的子串,只列舉可能包含答案的那些。其次通過記錄視窗內子串的一些資訊,使得判斷是否滿足條件的時間複雜度下降。
難點:什麼時候更新答案,視窗內儲存哪些資訊來降低判斷的時間。
正確性證明:分解為子問題,或者反證法證明視窗內一定包含過正確答案。
這一類題目的特點是:一開始子串就滿足條件,然後不斷擴大視窗直至不滿足條件,再縮小視窗直至滿足條件,每次擴大視窗並判斷滿足條件後都更新答案。
3. 無重複字元的最長子串:給定乙個字串,請你找出其中不含有重複字元的最長子串的長度。
解法:視窗的範圍為[left,right)
,用乙個字典記錄視窗中的字元及其出現次數,當視窗內的子串滿足條件時擴大視窗(right
右移)並更新答案,不滿足條件時縮小視窗(left
右移)。
正確性證明:實際上我們把問題分解為了多個子問題:求以s[i]
為起點的滿足條件的子串中最長的,再將子問題答案合併即可。
但還有乙個問題:left
每次並不是只向前走一步,如何保證列舉每乙個s[i]
呢?假設[left,right)
滿足條件,[left,right+1)
不滿足條件,此時答案至少是right-left
。left
向前走k
步直至滿足條件的過程中,以s[left]
為起點的滿足條件的子串,長度都不可能超過right-left
,也就是說這些子問題我們雖然沒有求,但它們的答案沒有意義,本身也就不需要求。
時間複雜度分析:left
和right
均沒有後退:o(n)。判斷right-left==len(window)
:o(1)。故總時間複雜度為o(n)。
**:
class
solution
:def
lengthoflongestsubstring
(self, s:
str)
->
int:
ls =
len(s)
window =
dict()
left = right =
0 ans =
0while rightc = s[right]
right +=
1 window[c]
= window.get(c,0)
+1while
len(window)
d = s[left]
left+=
1 window[d]-=1
if window[d]==0
: window.pop(d)
ans =
max(ans,right-left)
return ans
424. 替換後的最長重複字元
76. 最小覆蓋子串:給你乙個字串s
、乙個字串t
。返回s
中涵蓋t
所有字元的最小子串。如果s
中不存在涵蓋t
所有字元的子串,則返回空字串""
。
解法:視窗的範圍為[left,right)
,用乙個字典記錄視窗中的字元及其出現次數,當視窗內的子串不滿足條件時擴大視窗(right
右移),滿足條件時縮小視窗(left
右移)並更新答案。
正確性證明:類似上一題,把問題分解為子問題:求以s[i]
為終點的滿足條件的子串中最短的。
假設[left,right)
是第乙個滿足條件的區間,則所有終點小於right
的子問題都不必再考慮。之後依次列舉每乙個right
。但每次列舉時,left
並沒有從最左邊開始找,是否可能漏掉一些答案呢?其實和上一題類似,如果[left,right)
是當前子問題的最優解,則下乙個子問題從[left+1,right+1)
開始找,即使在left+1
左邊存在該子問題的解,對於最終的最優解也是沒有意義的,故不需要考慮。
時間複雜度分析:left
和right
均沒有後退:o(n)。判斷是否滿足條件:o(1)。故總時間複雜度為o(n)。
**:
class
solution
:def
minwindow
(self, s:
str, t:
str)
->
str:
ls,lt =
len(s)
,len
(t) need =
# t中的元素
for i in t:
need[i]
= need.get(i,0)
+1window =
# 視窗中的元素
left = right =
0# 視窗範圍
start = length =
float
('inf'
)# 答案
valid =
0# window是否包含t
while right# 更新視窗內資訊
c = s[right]
window[c]
= window.get(c,0)
+1if c in need and window[c]
== need[c]
: valid+=
1 right +=
1# 更新答案
while valid ==
len(need)
:if right - left < length:
start, length = left, right-left
c = s[left]
window[c]-=1
if c in need and window[c]
< need[c]
: valid -=
1 left +=
1return s[start:start+length]
if length<
float
('inf'
)else
''
滑動視窗為什麼快?實際上是因為它把原問題分解為了子問題,而其中很大一部分子問題是不需要求解的(最優解一定不出現在這些子問題之中)。故要求問題一定要有二段性質:
求最長時,對於任意子問題s[i]
,一定存在j>i
,使得s[i...k],i總而言之,求解最長問題的子問題s[left]
的過程中,right
向右移時要先滿足再不滿足;求解最短問題的子問題s[right]
的過程中,left
右移時也要先滿足再不滿足。這樣才可以用滑動視窗演算法。
395. 至少有k個重複字元的最長子串,由於不滿足上述條件,就無法直接使用滑動視窗演算法。
滑動視窗總結
滑動視窗思想 視窗由兩個指標構成,乙個左指標left,乙個右指標right,然後 left,right 表示的索引範圍是乙個視窗了。右指標right的功能是用來擴充套件視窗 當視窗內的條件沒有達到題目要求時,我們需要不斷移動右指標right直到視窗內的條件第一次滿足題目要求為止。左指標left的功能...
滑動視窗總結
本部落格基於大佬的題解 滑動視窗是一種高階的雙指標演算法,一般用於字串匹配問題 最小覆蓋子串 找到字串中所有字母異位詞 無重複字元的最長子串 字串的排列 滑動窗的基本思路就是維護乙個視窗,不斷滑動。該演算法的大致邏輯如下 int left 0,right 0 while right s.size 演...
滑動視窗總結
1.我們使用雙指標的思路,初始定義兩個指標,left right 0,把索引閉區間 left,right 看作乙個滑動視窗 2.然後不斷增加right的值,直到視窗中的字串符合要求 3.然後,停止增加right的值,轉而增加left的值,直到視窗中的字串不符合要求,每次增加left,就要更新一輪結果...