Overview of sliding Windows

Sliding Window method, also known as the ruler method, can be used to solve some problems of finding properties of continuous intervals (lengths, etc.) that satisfy certain conditions. Operating on a string or array of a particular size, rather than on the whole string or array, reduces the complexity of the problem. Thus, the nesting depth of the loop is reduced. Problems such as “please find xx of the most x interval (substring, subarray) satisfying XX” can be solved using this method.

  • Slide: indicates that the window is moving, that is, the movement is in a certain direction.
  • Window: the window size is not fixed, can be continuously expanded until certain conditions are met; You can also keep shrinking until you find the smallest window that meets the criteria; It could be a fixed size.

The basic idea of the algorithm:

A string can also be converted into an array. In fact, in essence, sliding Windows operate on an array. For the operation of an array, we generally use a circular class method, while for the sliding window method, in order to improve efficiency, we use an advanced circular method, that is, two Pointers: the left pointer left and the right pointer right.

The content between the two Pointers: [left…right] constitutes a window. With the continuous movement of the pointer, the position and size of the window will change, but the data in the window is always continuous, and the required results can be obtained by processing these data.

As shown in the figure below, set the size of the sliding window to 3. When the sliding window moves across the array each time, calculate the sum of the elements in the current sliding window and get the result RES:

Combined with the above ideas, we can design the general framework of sliding window, pseudo-code is as follows:

var list = [...]

var left = 0; / / pointer to the left
var right = 0; / / right pointer

var window= [] or {}while(right < list.length) { // The right pointer is smaller than the boundary
    window.add(list[right]);// Add elements to the window
    right++;// Move right to enlarge the window
    // If the window is completed, move left to narrow the window
    while (windowMeet the requirements (length <3)) {/ /... For window content processing
        sum(window) / / sum res

        window.remove(list[left]); // Move the element out of the window
        left++; // Zoom out}}Copy the code

The framework consists of two while loops, with the outer control window expanding and the inner control window shrinking. Here are a few examples.

Leetcode algorithm

3 The oldest string without repeating characters

Given a string s, find the length of the smallest string that does not contain repeating characters.

Example 1:

Input: s = "abcabcbb" Output: 3 Explanation: Since the oldest string without repeating characters is "ABC", its length is 3.Copy the code

Example 2:

Input: s = "BBBBB" Output: 1 Explanation: Since the oldest string without repeating characters is "b", its length is 1.Copy the code

Example 3:

Input: s = "pwwkew" Output: 3 Explanation: Since the oldest string without repeating characters is "wKE", its length is 3. Note that your answer must be the length of the substring, "pwke" is a subsequence, not a substring.Copy the code

Example 4:

Input: s = "output: 0Copy the code

First of all, we need to make it clear that substrings are generally continuous and subsequences are generally discontinuous, so when we solve for substrings, it’s easy to think of the sliding window solution.

The size of the window is not fixed. Therefore, we need to find a critical condition to determine when it is appropriate to adjust the data inside the window, that is, when the inner while loop is executed.

Since the title requires non-repeating characters, in many cases, Map is used to count whether characters in a string are repeated, that is, the key value is a single character, and the value is the number of occurrences of the character, as follows:

var str = 'abcdea'
var map = {}
for (var i = 0 ; i < str.length ; i++) {
    var cur = str[i]
    if (map[cur]) {
       map[cur] ++
    } else {
       map[cur] = 1}}map: {"a":2."b":1."c":1."d":1."e":1}
Copy the code

Therefore, if the value of a key in the map is greater than 1, it indicates that the string contains repeated characters. Using this idea, when we use the sliding window, it can be judged as the condition that the window meets the requirements. The overall idea is shown below (picture from Leetcode) :

Where, the Pointers I and J correspond to left and right respectively, and the length of continuous substring after each transformation is calculated by constantly changing the window.

Solution:

var lengthOfLongestSubstring = function(s) {
    var map = {} // Count the number of occurrences of each character
    var left = 0,right = 0;
    var len = s.length
    var res = 0
    while(right < len) {
        var r = s[right]
        right++
        // The right pointer element enters the window
        if (map[r]) {
            map[r]++
        } else {
            map[r] = 1
        }

        // Duplicate elements found in window
        while(map[r] > 1) {
            var l = s[left]
            // Zoom out
            left++
            map[l]--
        }
        // There must be no duplicate values in this window
        // Every time the window changes, the maximum length is recorded by pairwise comparison
        res = Math.max(res,right-left)
    }

    return res
};
Copy the code

Time complexity: O(N), where N is the length of the string. Left and right Pointers traverse the entire string once each.

Space complexity: O(N), where N is the number of non-repeating characters in the string, and space is consumed by Map.

239 Maximum sliding window

Given an integer array numS, there is a sliding window of size K that moves from the leftmost of the array to the rightmost of the array. You can only see k numbers in the sliding window. The sliding window moves only one bit to the right at a time.

Returns the maximum value in the sliding window.

Example 1:

Input: nums = [1, 3, 1, 3,5,3,6,7], k = 3 output:,3,5,5,6,7 [3] : The position of the sliding window Maximum -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- [1] 3-1-3 3 5 6 7 3 1 [3-1-3] 3 5 6 7 3 1 3 [1-3-5] 3 6 7 5 3-1/3-3 5 6 7 5 1 3 1-3 [5 3 6] 7 6 1 3 1-3 5 [3 6 7] 7Copy the code

Example 2:

Input: nums = [1], k = 1Copy the code

Example 3:

Input: nums = [1,-1], k = 1Copy the code

Example 4:

Input: nums = [9,11], k = 2 output: [11]Copy the code

Example 5:

Enter: nums = [4, -2], k = 2Output:4]
Copy the code

In LeetCode, this is a hard problem, but it is easy to provide a solution using our sliding window framework idea above.

First of all, the size of the window in the title is fixed, so we only need to move the window, that is, move the left and right Pointers synchronously, while the window in the inner while loop meets the requirements. It is very simple to directly determine whether the window length meets the requirements. The core code is as follows:

var window = []
while (right < nums.length) {
    var r = nums[right]
    right++
    window.push(r)// The right element enters the window
    while (window.length == k) { // The window length is k
	   max(window) // Calculate the maximum value in the window
        window.shift()// Move the left element out of the window
        left++ // Window moves to the right}}Copy the code

Basic idea:

  • Use the sliding window to traverse the number group.
  • The window object is an array to which elements are added each time the pointer moves to the right.
  • When the window size fitsk, calculate the maximum value in the window, and then the window as a whole moves to the right.
  • When the right pointer reaches the boundary, the traversal is complete.

Solution:

var maxSlidingWindow = function(nums, k) {
  if (k == 1) return nums
  var right = 0,left = 0
  var window = []
  var res = []

  while (right < nums.length) {
      var r = nums[right]
      right++
      window.push(r)
      while (window.length == k) {
          res.push(Math.max(... window))// use math.max to find the maximum value
          window.shift()
          left++
      }
  }

  return res
};
Copy the code

But as the topic of a hard difficulty, the difficulty lies in the time complexity, the above solution, every time we seek maximum data from a window, use Math. Max () method, it is actually very consumption, the performance of the underlying actually to traverse the window, and achieved to the maximum, such increased by a large part of the cycle time complexity.

We can think about, window window only need to get the maximum value, then we can set the window to a monotone decreasing array queue, when the new elements into the window than before hours, abandoned directly, then the maximum or the last maximum value, thus saving time, the introduction of monotone queue.

Monotonic queue basic idea:

Original queue: [1 3-1-3 5 3].

Queues are always maintained to keep them decreasing, so something like this happens:

operation The queue status
1 team [1]
3 in, bigger than 1, 1 out [3]
1 team (3, 1]
– 3 team [3, -1, -3]
5 join the queue, larger than -3, delete the previous elements, and then compare and delete [5]
3 team [5]

The code is as follows:

 class maxQueue {
    constructor(){
        this.items = []
    }
    // Enter the queue
    enqueue(ele){
      // Delete all previous elements smaller than the new element
      while(this.items.length && this.items[this.items.length-1] < ele) {
        this.items.pop()
      }
      this.items.push(ele)
    }
    // The team goes out first
    dequeue(ele){
      // Determine if the element needs to be removed
      if (this.items.length && this.items[0] == ele) {
        this.items.shift()
      }
    }
    // The head of the team is the biggest element
    front(){
      return this.items[0]}max(){
        return this.front()// The head of the team is the biggest element}}Copy the code

After introducing the priority queue, transform our overall solution as follows:

Solution:

var maxSlidingWindow = function(nums, k) {
    if (k == 1) return nums
    var right = 0,left = 0
    var window = new maxQueue()
    var res = []

    while (right < nums.length) {
        var r = nums[right]
        right++
        window.enqueue(r)
        while (right-left >= k) {
            var l = nums[left]
            res.push(window.max())
            window.dequeue(l)
            left++
        }
    }
    return res
};
Copy the code

** Time complexity: **O(N logN) where N is the length of the NUMS array. Left and right Pointers traverse the entire string once each. In the worst case, the elements in the array NUMS are monotonically increasing, so that the priority queue eventually contains all elements and no elements are removed. Since the time to put an element into the priority queue is O(log N), the total time is O(N logN).

** Space complexity: **O(N), where N is the space required by the priority queue.

76 Minimum coverage substring

I give you a string S, a string t. Returns the smallest string in S that covers all characters of t. Return an empty string “” if there is no substring in S that covers all characters of t.

Note:

  • For repeated characters in T, we must look for substrings that have at least the number of characters in t.

  • If such a substring exists in s, we guarantee that it’s the only answer.

Example 1:

Input: s = "ADOBECODEBANC", t = "ABC" output: "BANC"Copy the code

Example 2:

Input: s = "a", t = "a" output: "a"Copy the code

Example 3:

Input: s = "a", t = "aa" Output: "" Explanation: both characters 'a' in t should be included in the substring of S, so there is no qualified substring, return empty string.Copy the code

In the first string, you need to find the permutation of the second string. In the end, you need to find the substring. You can use the sliding window algorithm on the first string s.

First of all, the ultimate goal of the subject is to be found in the s t, but it has not and t are equal, but to find a t the permutation and combination of whether it contains all the elements in the t, because might contain duplicate elements in t, when calculating the permutation and combination, repeating element is not included in the statistics, or use of the Map, first calculate each character, the code is as follows:

var map = {}
var missingType = 0 // Record the number of characters that are not repeated

for (var c of t) {
    if (map[c]) {
        map[c]++
    } else {
        map[c] = 1
        missingType++
    }
}
Copy the code

After getting the map, you can use the map to match in S. The code is as follows:

while(right < s.length) {
    var r = s[right]
    right++ 
    
    if (map.hasOwnProperty(r)) { // Every time the character entering the window appears in s, the count is -1
        map[r]--
    }

    if (map[r] == 0) {// If the number of characters is 0, the current character is not missing
        missingType--
    }

    while(missingType == 0) { // If all characters match, you can shrink the window

        if (right - left < res.length) { / / length
            res = s.substring(left,right)
        }
        var l = s[left]
        left++
        map[l]++ // Each time a character moves out of the window, the count is increased by 1
        if (map[l] > 0) { // If the current character is greater than 0, the character is missing
            missingType++
        }
    }
}
Copy the code

We slide the window over S, expanding the window by moving the right pointer. When the window contains all the characters t needs, if it can be shrunk, we shrink the window until we get the minimum window, missingType is used to increase the flag bit to record repeated characters. The idea is as follows (image from LeetCode) :

Finally, we need to deal with some boundary conditions, such as s is equal to t, or s contains no T at all. The complete solution is as follows:

Solution:

var minWindow = function(s, t) {
    if (t.length > s.length) return ' '
    if (t == s) return s
    var flag = false
    var map = {}
    var missingType = 0
    var res = s
    for (var c of t) {
        if(! map[c]) { map[c] =1
            missingType++
        } else {
            map[c]++
        }
    }
    var left = 0,right = 0

    while(right < s.length) {
        var r = s[right]
        right++ 
        
        if (map.hasOwnProperty(r)) { // Every time the character entering the window appears in s, the count is -1
            map[r]--
        }
    
        if (map[r] == 0) {// If the number of characters is 0, the current character is not missing
            missingType--
        }
    
        while(missingType == 0) { // If all characters match, you can shrink the window
            flag = true // Find t in s
            if (right - left < res.length) { / / length
                res = s.substring(left,right)
            }
            var l = s[left]
            left++
            map[l]++ // Each time a character moves out of the window, the count is increased by 1
            if (map[l] > 0) { // If the current character is greater than 0, the character is missing
                missingType++
            }
        }
    }
    return! flag ?' ' : res
}
Copy the code

Time complexity: O(N), where N is the length of string S. Left and right Pointers traverse the entire string once each.

Space complexity: O(N), where N is the number of non-repeating characters in the string, and space is consumed by Map.

conclusion

Sliding window questions are frequently asked in interviews. The question itself is not complicated, so it is very important to master the framework.

Other articles:

Front end algorithm: maze problem

Front end algorithm: binary tree traversal

Front end algorithm: palindrome string