[Notes] Sliding Window

Posted by 西维蜀黍的OJ Blog on 2023-09-13, Last Modified on 2024-02-01

滑动窗口算法

说起滑动窗口算法,很多读者都会头疼。这个算法技巧的思路非常简单,就是维护一个窗口,不断滑动,然后更新答案么。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

567. Permutation in String

1343. Number of Sub-arrays of Size K and Average Greater than or Equal to Threshold

Ref