目錄
什麼是滑動窗口?
其實就是一個隊列,比如題中的 abcabcbb找出其中不含有重複字符的 最長子串 的長度,進入這個隊列(窗口)爲 abc 滿足題目要求,當再進入 a,隊列變成了 abca,這時候不滿足要求。所以,我們要移動這個隊列!
如何移動?
我們只要把隊列的左邊的元素移出就行了,直到滿足題目要求!
一直維持這樣的隊列,找出隊列出現最長的長度時候,求出解!
LeetCode-3. 無重複字符的最長子串
給定一個字符串,請你找出其中不含有重複字符的 最長子串 的長度。
示例 1:
輸入: "abcabcbb"
輸出: 3
解釋: 因爲無重複字符的最長子串是 "abc",所以其長度爲 3。
示例 2:輸入: "bbbbb"
輸出: 1
解釋: 因爲無重複字符的最長子串是 "b",所以其長度爲 1。
示例 3:輸入: "pwwkew"
輸出: 3
解釋: 因爲無重複字符的最長子串是 "wke",所以其長度爲 3。
請注意,你的答案必須是 子串 的長度,"pwke" 是一個子序列,不是子串。
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
if not s:return 0
left = 0
lookup = set()
n = len(s)
max_len = 0
cur_len = 0
for i in range(n):
cur_len += 1
while s[i] in lookup:
lookup.remove(s[left])
left += 1
cur_len -= 1
if cur_len > max_len:max_len = cur_len
lookup.add(s[i])
return max_len
下面介紹關於滑動窗口的萬能模板,可以解決相關問題,相信一定可以對滑動窗口有一定了解!
class Solution:
def lengthOfLongestSubstring(self, s):
"""
:type s: str
:rtype: int
"""
from collections import defaultdict
lookup = defaultdict(int)
start = 0 # 記錄當前子串的初始位置
end = 0 # end自增遍歷一遍整個字符串
max_len = 0 # 之前無重複最長子串
counter = 0 # 窗口移動標誌
while end < len(s):
if lookup[s[end]] > 0: # 有重複過的就進入循環
counter += 1
lookup[s[end]] += 1
end += 1
while counter > 0: # 窗口移動
if lookup[s[start]] > 1:
# 是否是窗口最左邊的字符重複:是的話只移動一次,不是的話一直移動直到重複字符在最左邊
counter -= 1
lookup[s[start]] -= 1
start += 1
max_len = max(max_len, end - start)
return max_len
n = Solution()
nn = n.lengthOfLongestSubstring('pwwkew')
LeetCode-76. 最小覆蓋子串
給你一個字符串 S、一個字符串 T,請在字符串 S 裏面找出:包含 T 所有字母的最小子串。
示例:
輸入: S = "ADOBECODEBANC", T = "ABC"
輸出: "BANC"
說明:如果 S 中不存這樣的子串,則返回空字符串 ""。
如果 S 中存在這樣的子串,我們保證它是唯一的答案。
看到題目之後的想法:
本問題要求我們返回字符串S 中包含字符串T 的全部字符的最小窗口。我們稱包含T 的全部字母的窗口爲可行窗口。
可以用簡單的滑動窗口法來解決本問題。
在滑動窗口類型的問題中都會有兩個指針。一個用於延伸現有窗口的 right指針,和一個用於收縮窗口的left指針。在任意時刻,只有一個指針運動,而另一個保持靜止。
本題的解法很符合直覺。我們通過移動right指針不斷擴張窗口。當窗口包含全部所需的字符後,如果能收縮,我們就收縮窗口直到得到最小窗口。
答案是最小的可行窗口。
舉個例子,S = "ABAACBAB",T = "ABC"。則問題答案是 "ACB" ,下圖是可行窗口中的一個。
算法:
初始,leftleft指針和rightright指針都指向SS的第一個元素.
將 rightright 指針右移,擴張窗口,直到得到一個可行窗口,亦即包含TT的全部字母的窗口。
得到可行的窗口後,將lefttleftt指針逐個右移,若得到的窗口依然可行,則更新最小窗口大小。
若窗口不再可行,則跳轉至 2。
具體代碼:
class Solution:
def minWindow(self, s: 'str', t: 'str') -> 'str':
from collections import defaultdict
lookup = defaultdict(int)
for c in t:
lookup[c] += 1
start = 0
end = 0
min_len = float("inf") # 無窮大
counter = len(t) # counter爲0時,開始窗口移動
res = ""
while end < len(s):
if lookup[s[end]] > 0: # 可行窗口中出現一次t中字符就就記錄一次
counter -= 1
lookup[s[end]] -= 1
end += 1
while counter == 0: # 移動窗口
if min_len > end - start:
min_len = end - start
res = s[start:end]
if lookup[s[start]] == 0:
# 行爲窗口中最左的字符是t中字符並且只在行爲窗口中出現一次就停止移動
counter += 1
lookup[s[start]] += 1
start += 1
return res
n = Solution()
nn = n.minWindow('ABAACBAB', 'ABC')
print(nn)
LeetCode-30. 串聯所有單詞的子串
給定一個字符串 s 和一些長度相同的單詞 words。找出 s 中恰好可以由 words 中所有單詞串聯形成的子串的起始位置。
注意子串要與 words 中的單詞完全匹配,中間不能有其他字符,但不需要考慮 words 中單詞串聯的順序。
示例 1:
輸入:
s = "barfoothefoobarman",
words = ["foo","bar"]
輸出:[0,9]
解釋:
從索引 0 和 9 開始的子串分別是 "barfoor" 和 "foobar" 。
輸出的順序不重要, [9,0] 也是有效答案。
示例 2:輸入:
s = "wordgoodgoodgoodbestword",
words = ["word","good","best","word"]
輸出:[]
一開始,我的想法是,每次從 s
截取一定長度(固定)的字符串,看這段字符串出現單詞個數是否和要匹配的單詞個數相等!
但是比如:s = "ababaab", words = ["ab","ba","ba"] 就會報錯!
錯誤原因:因爲計算時候我們會從字符串中間計算,也就是說會出現單詞截斷的問題。
所以我想另一種方法:
滑動窗口!
我們一直在 s
維護着所有單詞長度總和的一個長度隊列!
時間複雜度:O(n)O(n)
還可以再優化,只是加一些判斷,詳細看代碼吧!
class Solution:
def findSubstring(self, s, words):
from collections import Counter
if not s or not words:return []
one_word = len(words[0])
word_num = len(words)
n = len(s)
words = Counter(words)
res = []
for i in range(0, one_word): # 防止單詞截斷的問題
cur_cnt = 0 # 窗口中每出現一次單詞就加一,非words中單詞減一
left = i # 窗口左指針
right = i # 窗口右指針
cur_Counter = Counter() # 創建窗口
while right + one_word <= n: # 窗口右指針移動遍歷s
w = s[right:right + one_word]
right += one_word
cur_Counter[w] += 1
cur_cnt += 1
while cur_Counter[w] > words[w]:
# 窗口中出現非words中單詞或者words中單詞出現1次以上,需移動窗口左指針,移動到w的位置
left_w = s[left:left+one_word]
left += one_word
cur_Counter[left_w] -= 1
cur_cnt -= 1
if cur_cnt == word_num : # words中單詞已在窗口中全部出現
res.append(left)
return res
n = Solution()
nn = n.findSubstring("barfoothefoobarman", ["foo","bar"])
print(nn)
更新後代碼(好理解些):
class Solution:
def findSubstring(self, s, words):
from collections import Counter
if not s or not words:return []
one_word = len(words[0])
word_num = len(words)
n = len(s)
if n < one_word: return []
words = Counter(words)
res = []
for i in range(0, one_word): # 防止單詞截斷的問題
cur_cnt = 0 # 窗口中每出現一次單詞就加一,非words中單詞減一
left = i # 窗口左指針
right = i # 窗口右指針
cur_Counter = Counter() # 創建窗口
while right + one_word <= n: # 窗口右指針移動遍歷s
w = s[right:right + one_word]
right += one_word
# cur_Counter[w] += 1
# cur_cnt += 1
if w not in words:
left = right
cur_Counter.clear()
cur_cnt = 0
else:
cur_Counter[w] += 1
cur_cnt += 1
while cur_Counter[w] > words[w]:
# 窗口中出現非words中單詞或者words中單詞出現1次以上,需移動窗口左指針,移動到w的位置
left_w = s[left:left+one_word]
left += one_word
cur_Counter[left_w] -= 1
cur_cnt -= 1
if cur_cnt == word_num : # words中單詞已在窗口中全部出現
res.append(left)
return res
n = Solution()
nn = n.findSubstring("barfoothefoobarman", ["foo","bar"])
print(nn)
LeetCode-209. 長度最小的子數組
給定一個含有 n 個正整數的數組和一個正整數 s ,找出該數組中滿足其和 ≥ s 的長度最小的連續子數組。如果不存在符合條件的連續子數組,返回 0。
示例:
輸入: s = 7, nums = [2,3,1,2,4,3]
輸出: 2
解釋: 子數組 [4,3] 是該條件下的長度最小的連續子數組。
class Solution:
def minSubArrayLen(self, s, nums):
if sum(nums) < s:
return 0
if sum(nums) == s:
return len(nums)
left = 0
right = 0
sub_sum = 0
length = len(nums)
for right, item in enumerate(nums):
sub_sum += item
while sub_sum >= s:
length = min(length, right-left+1)
sub_sum -= nums[left]
left += 1
return length