滑动窗口算法
说起滑动窗口算法,很多读者都会头疼。这个算法技巧的思路非常简单,就是维护一个窗口,不断滑动,然后更新答案么。LeetCode 上有起码 10 道运用滑动窗口算法的题目,难度都是中等和困难。该算法的大致逻辑如下:
int left = 0, right = 0;
while (left < right && right < s.size()) {
// 增大窗口
window.add(s[right]);
right++;
while (window needs shrink) {
// 缩小窗口
window.remove(s[left]);
left++;
}
}
这个算法技巧的时间复杂度是 O(N),比字符串暴力算法要高效得多。
另外,虽然滑动窗口代码框架中有一个嵌套的 while 循环,但算法的时间复杂度依然是 O(N)
,其中 N
是输入字符串/数组的长度。
为什么呢?简单说,指针 left, right
不会回退(它们的值只增不减),所以字符串/数组中的每个元素都只会进入窗口一次,然后被移出窗口一次,不会说有某些元素多次进入和离开窗口,所以算法的时间复杂度就和字符串/数组的长度成正比。
解析
3. Longest Substring Without Repeating Characters
class Solution(object):
def lengthOfLongestSubstring(self, s):
"""
:type s: str
:rtype: int
"""
max_len = 0
l = 0
window = set()
for r in range(len(s)):
if s[r] not in window:
window.add(s[r])
max_len = max(max_len, r - l + 1)
continue
while l < r and s[l] != s[r]:
window.remove(s[l])
l += 1
l += 1
return max_len
ref
76. Minimum Window Substring
# solution 1 - sliding window
# - time: O(s), since one element in s will be push/pop to the window once respectively
# - space: O(1)
def minWindow(self, s: str, t: str) -> str:
tCount = {}
for char in t: # time: O(t)
tCount[char] = tCount.get(char, 0) + 1
tKeyCount = len(tCount) # the number of char to be satisfied
curMinLen = 0
res = ""
curWindow = {} # the window
curSatisfiedKeyCount = 0
l = 0
for r in range(len(s)): # time: O(s)
char = s[r]
curWindow[char] = curWindow.get(char, 0) + 1
if char in tCount and curWindow[char] == tCount[char]: # the number of char has satisfied
curSatisfiedKeyCount += 1
while curSatisfiedKeyCount == tKeyCount: # means that t is the substring of the current window
# update the result
if curMinLen == 0 or (curMinLen != 0 and (r - l + 1) < curMinLen): # found an eligible substring
res = s[l:r + 1]
curMinLen = r - l + 1
# shrink the window
curWindow[s[l]] -= 1
if s[l] in tCount and curWindow[s[l]] < tCount[s[l]]:
curSatisfiedKeyCount -= 1
l += 1
return res
ref
121. Best Time to Buy and Sell Stock
# solution 1
def maxProfit(self, prices: List[int]) -> int:
min_price = prices[0]
max_profit = 0
for r in range(1, len(prices)):
price = prices[r]
if price < min_price:
min_price = price
max_profit = max(max_profit, price - min_price)
return max_profit
# solution 2
def maxProfit(self, prices: List[int]) -> int:
max_profit = 0
l = 0
r = 1
while r < len(prices):
if prices[r] > prices[l]:
cur_profit = prices[r] - prices[l]
max_profit = max(max_profit, cur_profit)
else:
l = r
r += 1
return max_profit
ref
219. Contains Duplicate II
class Solution:
def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
l = 0
window = set()
for r in range(len(nums)):
if r - l > k:
window.remove(nums[l])
l += 1
if nums[r] in window:
return True
window.add(nums[r])
return False
- 给的k 就是滑动窗口的最大大小
- 如果当前滑动窗口,大于可以考虑的窗口范围,shrink the window first
- 用一个set来存储当前滑动窗口中已经存在的元素,在插入前先判断该元素是否已经存在于set中,如果存在,那么就返回true,否则插入该元素到set中
- 如果滑动窗口的大小大于k,那么就从set中删除滑动窗口最左边的元素
- ref
239. Sliding Window Maximum
TODO