LeetCode:滑動窗口問題解決方案集合(教程+Python代碼)

目錄

LeetCode-3. 無重複字符的最長子串

LeetCode-76. 最小覆蓋子串

LeetCode-30. 串聯所有單詞的子串

LeetCode-209. 長度最小的子數組


什麼是滑動窗口?

其實就是一個隊列,比如題中的 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

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章