Sliding window problem analysis
Use the sliding window framework to analyze and solve the classic algorithm problems in Leetcode/Likou.
Article directory
Basic operation of sliding window
# the iterable (list, str, ...) is "it"
# define a sliding window [left, right)
left = 0
right = 0
# define window states
wnd_state_1 = ...
wnd_state_2 = ...
...
# define target states
tgt_state_1 = ...
tgt_state_2 = ...
...
# define resulting state
result = ...
# expand window (moving right pointer)
expand_elem = it[right]
right += 1
# shrink window (moving left pointer)
shrink_elem = it[left]
left += 1
# define get_expand_condition() and get_shrink_condition()
# framework
while get_expand_condition() and right < len(it):
expand_elem = it[right]
right += 1
# update window states (expand) with expand_elem
while get_shrink_condition() and left <= right and left < len(it):
shrink_elem = it[left]
left += 1
# update window states (shrink) with shrink_elem
# update window states (expand) with expand_elem
# update resulting state
LC-3
Given a string s, please find the length of the longest substring that does not contain repeated characters.
Analysis:
The state of the window should be the set of characters in the window;
the expansion condition is unconditional (True);
the shrinking condition is that the character to be added to the window has already appeared in the character set of the window, and it needs to shrink until the character is not in the window The character set is in;
update the window state when expanding: characters are added to the set;
update the window state when shrinking: delete characters from the set;
def lengthOfLongestSubstring(self, s: str) -> int:
# define a sliding window [left, right)
left = 0
right = 0
# define window states
window = set()
# define resulting state
result = 0
# get_expand_condition() = True
while right < len(s):
# expand window (moving right pointer)
expand_elem = s[right]
right += 1
# get_shrink_condition() = expand_elem in window
while expand_elem in window and left <= right and left < len(s):
# shrink window (moving left pointer) with shrink_elem
shrink_elem = s[left]
left += 1
# update window states (shrink)
window.remove(shrink_elem)
# update window states (expand) with expand_elem
window.add(expand_elem)
# update resulting state
result = max(result, right - left)
return result
LC-438
Given two strings s and p, find all anagram substrings of p in s, and return the starting indices of these substrings. The order in which answers are output is not considered.
An anagram refers to a character string (including the same character string) formed by rearranging the same letter.
Analysis:
The state of the window is the characters and counts in the window;
the target state is the characters and counts of the pattern string p;
the expansion condition is unconditional (True);
the contraction condition is that the size of the window exceeds the length of the given pattern string p, Since the right will only move one space at a time, this condition will only move one space at a time;
update the window state when expanding: add 1 to the count of the key, and create it if there is no key;
update the window state when shrinking: set the count to -1 , if it is 0, delete it;
def findAnagrams(self, s: str, p: str) -> List[int]:
string_length = len(s)
# define a sliding window [left, right)
left = 0
right = 0
# define window states
window_char_and_count = dict()
# define target states
target_char_and_count = dict()
for _ in p:
target_char_and_count[_] = target_char_and_count.setdefault(_, 0) + 1
# define resulting state
result = []
# get_expand_condition() = True
while right < string_length:
# expand window (moving right pointer)
expand_elem = s[right]
right += 1
# update window states (expand) with expand_elem
window_char_and_count[expand_elem] = window_char_and_count.setdefault(expand_elem, 0) + 1
# get_shrink_condition() => size of slideing window == len(p)
while right - left > len(p):
shrink_elem = s[left]
left += 1
# update window states (shrink) with shrink_elem
if shrink_elem in window_char_and_count:
window_char_and_count[shrink_elem] -= 1
if window_char_and_count[shrink_elem] == 0:
window_char_and_count.pop(shrink_elem)
# update resulting state
if window_char_and_count == target_char_and_count:
result.append(left)
return result
LC-76
You are given a string s and a string t. Returns the smallest substring in s that covers all characters of t. Returns the empty string "" if there is no substring in s that covers all characters of t.
Analysis:
The state of the window is the characters and counts in the window and the types of characters that meet the target conditions; the
target state is the characters, counts, and types of characters of the pattern string p; the
contraction condition is that the types and quantities of characters meet the requirements;
update the window state when expanding: Character count +1, if this character meets the requirements of the target condition, the type of character that meets the target condition +1;
update the window state when shrinking: character count -1, if this character returns to the requirement of the target condition, the type of character that meets the target condition charkind-1;
def minWindow(self, s: str, t: str) -> str:
s_len = len(s)
# define a sliding window [left, right)
left = 0
right = 0
# define window states
window_char_and_count = dict()
window_valid_distinct_char_count = 0
# define target states
target_char_and_count = dict()
for ch in t:
target_char_and_count[ch] = target_char_and_count.setdefault(ch, 0) + 1
target_distinct_char_count = len(target_char_and_count)
# define resulting state
result = None
while right < s_len:
# expand the window
expand_elem = s[right]
right += 1
# update window state (expand) with expand_elem
if expand_elem in target_char_and_count:
window_char_and_count[expand_elem] = 1 + window_char_and_count.setdefault(expand_elem, 0)
if window_char_and_count[expand_elem] == target_char_and_count[expand_elem]:
window_valid_distinct_char_count += 1
# get_shrink_condition() => window_valid_distinct_char_count == target_distinct_char_count
while window_valid_distinct_char_count == target_distinct_char_count:
# update resulting state
if not result:
result = s[left:right]
else:
if len(result) > right - left:
result = s[left:right]
# shrink the window
shrink_elem = s[left]
left += 1
# update window state (shrink)
if shrink_elem in window_char_and_count:
if window_char_and_count[shrink_elem] == target_char_and_count[shrink_elem]:
window_valid_distinct_char_count -= 1
window_char_and_count[shrink_elem] -= 1
if window_char_and_count[shrink_elem] == 0:
window_char_and_count.pop(shrink_elem)
if result == None:
return ""
else:
return result
LC-713
Given an integer array nums and an integer k, please return the number of consecutive subarrays whose product of all elements in the subarray is strictly less than k.
Analysis:
The state of the window is the product of the numbers in the window;
the contraction condition is that the product is greater than or equal to the specified value and there is at least one element in the window;
update the window state when expanding: update product *= expansion element;
update window state when shrinking: update product/ = shrink element;
def numSubarrayProductLessThanK(self, nums: List[int], k: int) -> int:
n_len = len(nums)
# define a sliding window [left, right)
left = 0
right = 0
# define window states
window = 1
# define resulting state
result = 0
while right < n_len:
# expand window
expand_elem = nums[right]
right += 1
# update window state
window *= expand_elem
while window >= k and right > left:
# shrink window
shrink_elem = nums[left]
left += 1
# update window state
window /= shrink_elem
# update resulting state
result += right - left
return result
LC-1438
Given an integer array nums and an integer limit representing the limit, please return the length of the longest continuous subarray, the absolute difference between any two elements in the subarray must be less than or equal to limit.
Analysis:
The state of the window is the maximum and minimum values in the window, but because the maximum and minimum values need to be updated during the sliding process, two monotonic queues are needed; the contraction condition is that the
absolute difference between the maximum and minimum values in the window is greater than limit;
update the window state when expanding: update the monotonic queue to obtain the latest maximum and minimum values;
update the window state when shrinking: update the monotonic queue to obtain the latest maximum and minimum values;
def longestSubarray(self, nums: List[int], limit: int) -> int:
nums_length = len(nums)
# define sliding window [left, right)
left = 0
right = 0
# define window states
window_min_queue = collections.deque()
window_max_queue = collections.deque()
# define resulting state
result = 0
while right < nums_length:
expand_elem = nums[right]
right += 1
# add to mono queue - min values
while window_min_queue.__len__() > 0 and window_min_queue[-1] > expand_elem:
window_min_queue.pop()
window_min_queue.append(expand_elem)
# add to mono queue - max values
while window_max_queue.__len__() > 0 and window_max_queue[-1] < expand_elem:
window_max_queue.pop()
window_max_queue.append(expand_elem)
while window_max_queue[0] - window_min_queue[0] > limit:
shrink_elem = nums[left]
left += 1
# remove from mono queue
if shrink_elem == window_min_queue[0]:
window_min_queue.popleft()
if shrink_elem == window_max_queue[0]:
window_max_queue.popleft()
result = max(result, right - left)
return result
LC-424
You are given a string s and an integer k. You can select any character in the string and change it to any other uppercase English character. This operation can be performed at most k times.
After performing the above operations, return the length of the longest substring containing the same letter.
Analysis:
The state of the window is a dictionary of the type and quantity of characters in the window;
the contraction condition is that each type of character in the window + k < the length of the window;
update the window state when expanding: update the character count + 1, if there is no dictionary item Then create;
update the window state when shrinking: update the character count -1, if it is 0, delete the dictionary item;
note that since the shrinking condition is not clear in one sentence, you need to use the flag variable to store it separately, and update it in the while value.
def characterReplacement(self, s: str, k: int) -> int:
# sliding window [left,right)
left = 0
right = 0
s_len = len(s)
# window state is character and counts
window = dict()
# resulting state
result = 0
while right < s_len:
expand_elem = s[right]
window[expand_elem] = window.setdefault(expand_elem, 0) + 1
right += 1
# get_shrink_condition() is complex and should be replaced with flag
flag = False
while flag == False: # while False condition
window_length = right - left # get condition
for winKey in window:
if window_length - window[winKey] <= k:
flag = True
result = max(result, window_length)
# shrink window
if flag == False:
shrink_elem = s[left]
left += 1
window[shrink_elem] -= 1
if window[shrink_elem] == 0:
window.pop(shrink_elem)
return result
Variation of sliding window
# the iterable (list, str, ...) is "it"
# define two sliding windows [0:left) and (right:]
left = 0
right = 0
# expand window [0:left) (moving left pointer)
expand_elem = it[left]
left += 1
# shrink window [0:left) (moving left pointer)
left -= 1 # note that moving pointer first if move towards opposite side
shrink_elem = it[left]
# expand window (right:] (moving right pointer)
expand_elem = it[right]
right -= 1
# shrink window (right:] (moving right pointer)
right += 1 # note that moving pointer first if move towards opposite side
shrink_elem = it[right]
LC-1658
You are given an integer array nums and an integer x. For each operation, you should remove the leftmost or rightmost element of the array nums and then subtract the value of that element from x. Note that the array needs to be modified for subsequent operations.
If it is possible to reduce x to exactly 0, return the smallest operand; otherwise, return -1.
Analysis:
In fact, the values in the two windows [0:left) and (right:] are only summed to be the minimum window length of x, so the general idea is:
- Expand the left window first until the sum reaches or exceeds x
- Shrink the left window one element at a time
- Dilate the right window until the sum reaches or exceeds x
def minOperations(self, nums, x: int) -> int:
nums_length = len(nums)
# define two windows = [0:left) and (right:]
left = 0
right = nums_length - 1
# define window state (sum of nums in window)
window = 0
# define resulting state
result = nums_length + 1
#-----------expand [0:left) till sum reaches x---------
# condition: sum of window < x
# restriction: left < nums_length
while window < x and left < nums_length:
expand_elem = nums[left]
window += expand_elem
left += 1
# update resulting state
if window == x:
result = min(result, left)
#-------------shrink [0:left) and expand (right:]----------
# restriction: two windows cannot overlap (when left = right + 1, the two windows [0:right+1) and (right:] ==> [0:right] and (right:] ==> [0:]
while right >= left - 1:
expand_elem = nums[right]
window += expand_elem
right -= 1
# condition sum of window > x
# restriction: left > 0
while window > x and left > 0:
left -= 1 # shrink, deduce index first and then remove element
shrink_elem = nums[left]
window -= shrink_elem
# update resulting state
if window == x:
result = min(result, left + nums_length - 1 - right)
if result == nums_length + 1:
return -1
else:
return result
LC-209
Given an array of n positive integers and a positive integer target.
Find the continuous subarray [numsl, numsl+1, …, numsr-1, numsr] with the smallest length satisfying its sum ≥ target in this array, and return its length. Returns 0 if no matching subarray exists.
Analysis idea 1:
The left side of the sliding window is fixed, and the right side has been expanded until the condition is met, the left side is reduced by one grid at a time, and then the right side is continued to expand
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
nums_length = len(nums)
# sliding window [left,right)
left = 0
right = 0
# window state = sum of nums in window
window = 0
# resulting state
result = nums_length + 1
while left <= right and left < nums_length:
# expand right side till sum>=target
while right < nums_length and window < target:
expand_elem = nums[right]
window += expand_elem
right += 1
if window >= target:
result = min(result, right - left)
# shrink left side
shrink_elem = nums[left]
window -= shrink_elem
left += 1
if result == nums_length + 1:
return 0
else:
return result
Analysis idea 2:
expand the right side normally, when the condition is met, start to shrink the left side, and update the result each time
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
nums_length = len(nums)
left = 0
right = 0
window = 0
result = nums_length + 1
while right < nums_length:
expand_elem = nums[right]
window += expand_elem
right += 1
while window >= target:
result = min(result, right - left)
shrink_elem = nums[left]
window -= shrink_elem
left += 1
if result == nums_length + 1:
return 0
else:
return result