leetcode刷題記錄:演算法(三)滑動視窗演算法

qq_39561198發表於2020-10-28

滑動視窗是為了減少迴圈次數。
滑動視窗有幾個應用場景:

  1. 給定一個整數陣列,計算長度為 ‘k’ 的連續子陣列的最大總和。
    這中應用情況下,視窗大小是固定的,視窗每移動一步,視窗中的數值和上一步中的有重合,這個重合的部分不再參與計算,滑動視窗就是利用這個來將計算量減少。
  2. 給定一個字串S和一個目標字串T,找到S中包含所有T中字母的最小子字串。
    這種情況下,視窗大小是變化的。先設定視窗大小為0,然後移動右側邊界,逐步擴張視窗,直至能滿足要求。
    然後嘗試從左側減小視窗,如果不再滿足要求,就繼續從右側擴張視窗。
    以此迴圈,可以得到最後的結果。

繼續刷題,還是飼養員up推薦的三道題。
3.無重複字元的最長子串
寫的有點複雜,搞了一個flag將情況分為移動左視窗和移動右視窗,還加了一個判斷特定情況的條件(是否是空字串)。

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        if len(s)==0:
            return 0
        s_left = 0
        s_right = 0
        max_len = 1
        flag = 0  ##0時移動右視窗,1時移動左視窗
        window = s[s_left]
        while (s_right < len(s)-1):
            if flag == 0:
                s_right += 1
                if s[s_right] not in window:
                    window = s[s_left:s_right+1]
                    if len(window) > max_len:
                        max_len = len(window)
                elif s[s_right] in window:
                    flag = 1
                    window = s[s_left:s_right+1]
            else:
                s_left += 1
                if s[s_left - 1] == s[s_right]:
                    flag = 0
                    window = s[s_left:s_right+1]
        return max_len

官方給的答案如下,比我的答案短很多,但是執行起來時間和記憶體佔用比我的更拉胯。
其中,官方答案利用了一個雜湊集合,我一直用的列表切片。
可以學習到的幾點:

  1. 如何避免特殊情況,使得程式碼應用更廣泛(我自己寫的裡面對空字串和單字元的情況做了另外處理)
  2. 取最大值不要用if了,很蠢,python給提供了max函式竟然不用(嘆氣)
  3. 官方給的例子用了巢狀迴圈,減少了程式碼量,我則是通過flag來對右視窗進行多次移動的,即多次重複操作的時候,可以考慮用個迴圈代替flag。
class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        # 雜湊集合,記錄每個字元是否出現過
        occ = set()
        n = len(s)
        # 右指標,初始值為 -1,相當於我們在字串的左邊界的左側,還沒有開始移動
        rk, ans = -1, 0
        for i in range(n):
            if i != 0:
                # 左指標向右移動一格,移除一個字元
                occ.remove(s[i - 1])
            while rk + 1 < n and s[rk + 1] not in occ:
                # 不斷地移動右指標
                occ.add(s[rk + 1])
                rk += 1
            # 第 i 到 rk 個字元是一個極長的無重複字元子串
            ans = max(ans, rk - i + 1)
        return ans

209.長度最小的子陣列
好傢伙,我在上一個程式碼的基礎上修修補補,最後擊敗了全國百分之11的對手,垃圾製造者就是我了。

class Solution:
    def minSubArrayLen(self, s: int, nums: List[int]) -> int:
        # 這裡採用list了,不用set了,set的特性是沒有重複
        occ = list()
        ans = list()
        n = len(nums)
        if n == 0:
            return 0
        # 右指標,初始值為 -1,相當於我們在字串的左邊界的左側,還沒有開始移動
        rk = -1
        for i in range(n-1):
            if i != 0:
                # 左指標向右移動一格,移除一個字元
                occ.pop(0)
            while rk + 1 < n and sum(occ) < s:
                # 不斷地移動右指標
                occ.append(nums[rk + 1])
                rk += 1
            # 第 i 到 rk 個字元是一個極長的無重複字元子串
            if sum(occ) >= s:
                ans.append(rk - i + 1)
        return 0 if len(ans)==0 else min(ans)

改了一遍,稍微好了一點,不過用for i in range(n)確實可以排除空集特殊情況:

class Solution:
    def minSubArrayLen(self, s: int, nums: List[int]) -> int:
        if len(nums) == 0:
            return 0
        s_left = 0
        s_right = -1
        sum_sub = 0
        min_len = list()
        flag = 0    ##  0時移動右視窗,1時移動左視窗
        while (s_right < len(nums)):
            if flag == 0 and s_right <len(nums)-1:
                ## 移動右視窗
                s_right += 1
                ## 更新和
                sum_sub += nums[s_right]
                ## 如果此時和不滿足要求,那麼flag不變,繼續移動右視窗
                ## 如果此時和滿足要求,那麼記錄長度,並開始移動左視窗
                if sum_sub >= s:
                    min_len.append(s_right-s_left+1)
                    flag = 1
            else:
                ## 移動左視窗
                s_left += 1
                ## 更新和
                sum_sub -= nums[s_left-1]
                ## 如果此時和不滿足要求,那麼停止移動左視窗,開始移動右視窗
                if sum_sub < s:
                    flag = 0
                ## 如果此時和滿足要求,那麼記錄長度,並繼續移動左視窗
                else:
                    min_len.append(s_right-s_left+1)
                if s_left == s_right:
                    break
        return 0 if len(min_len)==0 else min(min_len)

相關文章