Sliding window problem analysis

Sliding window problem analysis

Use the sliding window framework to analyze and solve the classic algorithm problems in Leetcode/Likou.

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

Guess you like

Origin blog.csdn.net/O_1CxH/article/details/126813986