跳至主要内容

Sliding Window

預備知識

滑動視窗(Sliding Window)是 Two Pointers 的一種特殊應用,專門用來處理 「連續」子陣列或子字串的問題。

它維護一個左右邊界(也就是一段連續的區間,稱為 Window),讓這個區間像窗戶一樣在陣列上移動,藉此避免對每一種可能的子區間都重新計算一次,把原本 O(n^2) 甚至 O(n^3) 的暴力解降到 O(n)。

白話理解

想像坐火車看窗外風景,車廂的窗戶(Window)固定大小,隨著火車前進,窗戶裡看到的風景會持續「新增前方的景色、移除後方已經看過的景色」,而不需要每次都把整條鐵路重新看一遍。

兩種類型​

1. Fixed Size Window(固定大小視窗)​

視窗大小固定,只需要一路往右滑動。

範例:找出陣列中「連續 k 個數字」的最大總和。

def max_subarray_sum(arr, k):
window_sum = 0

# 先計算第一個視窗(前 k 個元素)的總和
for i in range(k):
window_sum += arr[i]

max_sum = window_sum

# 視窗往右滑動一格:加入新進來的元素,扣掉被擠出去的元素
for i in range(k, len(arr)):
window_sum += arr[i] - arr[i - k]
max_sum = max(max_sum, window_sum)

return max_sum

逐步拆解:以 arr = [2, 1, 5, 1, 3, 2]、k = 3 為例​

視窗範圍windowSum說明
[2, 1, 5]8初始視窗(前 3 個元素),maxSum = 8
[1, 5, 1]8 - 2 + 1 = 7扣掉滑出去的 2,加上滑進來的 1
[5, 1, 3]7 - 1 + 3 = 9maxSum 更新為 9
[1, 3, 2]9 - 5 + 2 = 6沒有超過目前的 maxSum

最終答案是 9。整個過程完全不需要重新加總整個視窗,只需要「加一個、減一個」,這正是 Sliding Window 比暴力法快的關鍵。

2. Variable Size Window(動態大小視窗)​

視窗大小會依條件伸縮:先擴張右邊界,直到不符合條件,再收縮左邊界。

範例:Leetcode: Longest Substring Without Repeating Characters,找出字串中最長「不重複字元」的連續子字串長度。

def length_of_longest_substring(s):
seen = {} # 記錄每個字元「上一次出現」的 index
left = 0
max_length = 0

for right in range(len(s)):
char = s[right]

# 如果這個字元曾經出現過,且出現位置在目前視窗範圍內,就把左邊界收縮到重複字元的下一格
if char in seen and seen[char] >= left:
left = seen[char] + 1

seen[char] = right
max_length = max(max_length, right - left + 1)

return max_length
新手小提醒

動態視窗的核心口訣是:先無腦擴張右邊界,發現不符合條件了,才收縮左邊界。收縮時要用 while(可能需要連續收縮好幾次)而不是 if(只收縮一次),視題目條件而定;上面這題因為一次只會有一個重複字元,用 if 也足夠。

複雜度​

類型時間複雜度空間複雜度
Fixed Size WindowO(n)O(1)
Variable Size WindowO(n)視情況而定,通常是 O(k)(k 為視窗內需要記錄的相異元素種類數)

雖然動態視窗看起來像雙層迴圈(右指標一層、左指標一層),但因為左指標一輩子最多只會往右移動 n 次,整體還是 O(n) 而不是 O(n^2)。

適用情況​

  • 固定長度的連續子區間統計:例如固定視窗大小的最大/最小總和、平均值。
  • 尋找符合條件的最長 / 最短連續子區間:例如不重複字元的最長子字串、和至少為 k 的最短子陣列。
  • 字元出現次數限制類問題:例如「最多可以替換 k 個字元」的最長重複字元子字串,通常會搭配一個 Hash Table 或固定大小的陣列來記錄視窗內的字元次數。

常見誤區​

  • 該用 Variable Window 卻用 Fixed Window:如果題目要求「找出符合條件的區間,但長度不確定」,代表要用動態視窗,先確認題目問的是「固定長度」還是「最長 / 最短」。
  • 收縮視窗的時機寫錯:忘記在右邊界擴張「之後」才檢查是否需要收縮左邊界,或是該用 while 卻寫成 if,導致視窗內殘留不符合條件的元素。
  • 忘記同步更新輔助的統計資料:視窗左右邊界移動時,如果視窗內用了額外的 Hash Map 或計數陣列記錄元素出現次數,必須同步在「新增進視窗」與「移出視窗」時更新,忘記其中一邊會讓統計資料失真。
  • 和 Prefix Sum 搞混使用時機:如果題目需要「多次查詢」任意(不一定連續變動的)區間和,Prefix Sum 通常更合適;Sliding Window 則更適合「視窗會隨條件連續變化」的場景。