程式碼隨想錄陣列二刷:長度最小的子陣列(滑動視窗)
leetcode209
這道題採用滑動視窗的思想去做。
實現滑動視窗,主要確定如下三點:
- 視窗內是什麼?
- 如何移動視窗的起始位置?
- 如何移動視窗的結束位置?
視窗就是 滿足其和 ≥ s 的長度最小的 連續 子陣列。視窗的起始位置如何移動:如果當前視窗的值大於等於s了,視窗就要向前移動了(也就是該縮小了)。視窗的結束位置如何移動:視窗的結束位置就是遍歷陣列的指標,也就是for迴圈裡的索引。
具體程式碼如下:
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
l = len(nums)
left,right = 0,0
min_len = float('inf')
cur_sum = 0
while right < l:
cur_sum += nums[right]
while cur_sum >= target:
min_len = min(min_len, right - left + 1)
cur_sum -= nums[left]
left += 1
right += 1
return min_len if min_len!=float('inf') else 0
接下來來具體分析這個程式碼,因為是滑動視窗,那麼這個視窗是怎麼構成的呢,是不是就是left和right組成的區間長度,那麼何時去計算這個長度,是不是區間裡面的和大於等於target時候開始去計算長度。也就是:while cur_sum >= target
,然後左邊開始移動,移動的原因是因為這個和可能遠超你要求的target,所以可以移動左邊來縮小視窗。由於你左邊移動了,就要把個值從總體的和剔除掉。那麼啥時候退出這個內部迴圈呢。也就是求出的和小於target時候,就去移動右邊界。
下面來看水果成籃這道題:
leetcode904
這道題也是採用滑動視窗去解決問題。在做這道題時候,要明白幾個問題才能去做好這道題
- 視窗內是什麼?
- 如何移動視窗的起始位置?
- 如何移動視窗的結束位置?
這道題視窗內就是能摘的樹的數量。但是陣列裡面的數字相當於是標記一樣,這裡要用到雜湊表去記錄一下。因為最多兩個籃子,所以種類最多是兩種。當超過兩個籃子時候,左邊視窗開始移動,同時雜湊表關於該項的值要減去1,如果該項的值為0,則刪除這一項。
具體程式碼如下:
class Solution:
def totalFruit(self, fruits: List[int]) -> int:
count = collections.defaultdict(int)
left,max_len,right = 0,0,0
while right < len(fruits):
count[fruits[right]] += 1
while len(count) > 2:
count[fruits[left]] -= 1
if count[fruits[left]] == 0:
del count[fruits[left]]
left += 1
max_len = max(max_len, right - left + 1)
right += 1
return max_len
程式碼步驟解析
-
初始化變數:
count
:defaultdict(int)
,用於記錄視窗內每種水果的數量。left
和right
:視窗的左右邊界。max_len
:記錄遇到的最大視窗長度。
-
擴充套件視窗:
- 使用
right
指標遍歷fruits
陣列,每次迴圈中將fruits[right]
加入到視窗(即雜湊表count
中增加對應水果的計數)。
- 使用
-
調整視窗大小:
- 當雜湊表中的鍵(即水果型別)數量超過 2 時,開始移動
left
指標縮小視窗,直到視窗內的水果型別數不超過兩種。 - 如果某種水果的數量減到 0,則從雜湊表中刪除該水果。
- 當雜湊表中的鍵(即水果型別)數量超過 2 時,開始移動
-
更新最大長度:
- 在每次迴圈的末尾,更新
max_len
為當前視窗的長度(right - left + 1
)和已記錄的max_len
中的較大值。
- 在每次迴圈的末尾,更新
-
返回結果:
- 迴圈結束後,返回
max_len
作為最大可以摘的水果數量。
- 迴圈結束後,返回
效能分析
- 時間複雜度:O(N),其中 N 是
fruits
陣列的長度。儘管內部有一個 while 迴圈來調整視窗大小,但每個元素最多被加入和刪除一次。 - 空間複雜度:O(1),雖然使用了雜湊表,但由於最多隻儲存兩種水果的計數,所以空間複雜度為常數。
下面繼續來看一道題;
leetcode76
這道題也是用滑動視窗+雜湊表去做。首先s的長度小於t的長度是一定不滿足的,直接返回空字串即可。
外層while迴圈依舊控制視窗右邊界,內層迴圈控制左邊界。只不過這道題需要用到雜湊表記錄t對應字元個數。具體程式碼如下:
class Solution:
def minWindow(self, s: str, t: str) -> str:
if len(s) < len(t):
return ""
hash_t = collections.defaultdict(int)
hash_s = collections.defaultdict(int)
min_len = float('inf')
min_window = ""
left,right,formed= 0,0,0
for i in range(len(t)):
hash_t[t[i]] += 1
hash_t_lenth = len(hash_t)
while right < len(s):
hash_s[s[right]] += 1
if s[right] in hash_t and hash_s[s[right]] == hash_t[s[right]]:
formed += 1
while formed == hash_t_lenth:
if right - left + 1 < min_len:
min_len = right - left + 1
min_window = s[left:right+1]
hash_s[s[left]] -= 1
if hash_s[s[left]] < hash_t[s[left]]:
formed -= 1
left += 1
right += 1
return min_window
下面繼續看幾道滑動視窗相關的題目:
leetcode3
具體程式碼如下:
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
left,right = 0,0
maxlen = 0
hashmap = collections.defaultdict(int)
while right < len(s):
hashmap[s[right]] += 1
while hashmap[s[right]] > 1:
hashmap[s[left]] -= 1
if hashmap[s[left]] == 0:
del hashmap[s[left]]
left += 1
maxlen = max(maxlen, right - left + 1)
right += 1
return maxlen