Sliding Window


Sliding windows enable naive quadratic+ algorithms to be more efficient, usually reducing the exponent, i.e. quadratic solutions often become linear, etc. They work by expanding & contracting a window as it slides across an array or string. Frequently, it is necessary to compute some quantity along the way in search of the maximum value.

Algorithm

Initialize l & r, the left & right edges of the sliding window, a hashmap|set|variant called window, for tracking the frequency|quantity needed to test for inclusion in the window, & max|etc, to track any quantity needing to be computed on the window. TODO: update this after experiencing more sliding window examples.

while r < n:  
  if a[r] meets the criteria for joining the sliding window:
    add a[r] to the window by membership|calculation|variant
    slide r right
    update max|etc to track quantity computed on the window
  else:
    remove[a[l]] from the window
    slide l right
    update running quantity as needed

  return max|etc

Problems

 

Maximum Length Subarray with at most K frequency


Given an integer array & frequency k, return the maximum length subarray with no element of frequency greater than k.

Explore

              k=1            k=0            k=1
              1 2 3 4 1 5    1 2 3 4 1 5    1 2 3 4 3 5
              1 2 3 4          max=0        1 2 3 4
                2 3 4 1 5                         4 3 5
                  max=5                         max=4
  1. each time we move r, increment count & counts[a[i]] & check for new max length subarray.
  2. when moving l, decrement the count & counts[a[i]]

Python Solution

class Solution:
    def maxSubarrayLength(self, nums: List[int], k: int) -> int:
        a, n, count, max_sa, l, r, counts = nums, len(nums), 0, 0, 0, 0, Counter()
        while r < n:
            if counts[a[r]] < k:
                counts[a[r]] += 1
                count += 1
                r += 1
                max_sa = max(count, max_sa)
            else:
                counts[a[l]] -= 1
                count -= 1
                l += 1
 
        return max_sa

Complexity Analysis

This particular sliding window requires at worst 2 passes through the input using O(n) time & O(n) space to keep track of what is in the window.

C++ Solution

TODO: c++ solution

 

Count Subarrays Where Max Element Appears at Least K Times


Given an array of integers & a positive integer k, return the number of subarrays which contain the maximum element of the input array at least k times.

Explore

k=2
1 0 0 1 1 0 0 1
  s   e          1
       se        4
        s e      4
        s   e    4
          s   e  5
            s = 18

The idea is to count the number of valid subarrays ending at the ith index.

  1. Iterate through all i in the input array counting the valid subarrays at ending at i.
  2. Collapse from the left until the window is no longer valid.
  3. Add this index, l, to the number of valid subarrays each valid subarray, (l,r), will have l valid subarrays to add to the total. Once l moves right of a valid subarray, l will be the correct addition due to zero indexing.
  4. Continue step #1.

Python Solution

class Solution:
    def countSubarrays(self, nums: List[int], k: int) -> int:
        a, n, soln, l, r, kcount, smax = nums, len(nums), 0, 0, 0, 0, max(nums)
        for r in range(n):  # count subarrays ending at i -> r must try every i
            if a[r] == smax:  # same as while w/ a[r] meets always criteria
                kcount += 1
            while kcount == k:  # kcount !> k -> k > 0
                if a[l] == smax:
                    kcount -= 1
                l += 1
            soln += l
 
        return soln

Complexity Analysis

We traverse the input array linearly visiting each index at most twice with the edges of our window & keeping track of the count of subarrays & maximum values which is O(n) time & O(1) space.  

Longest Substring Without Repeating Characters


Given a string, return the longest substring without repeating characters.

Explore

abcdefgabcd
12345677777

Python Solution

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        a, n, l, r, smax, window = a, len(s), 0, 0, 0, set()
        while r < n:
            if a[r] not in window:
                window.add(a[r])
                r += 1
                smax = max(smax, r - l)
            else:
                window.remove(a[l])
                l += 1
 
        return smax

 

Find the Most Frequent Possible Integer With K Operations


Given an Array & K Increment Operations, find the integer with the highest possible frequency after K operations on the given array.

Explore

k = 5
9 2 3 4 5 6
2 3 4 5 6 9
2
2 3
2 3 4
  3 4 5
    4 5 6
        6 9
  1. Sort the array from least to greatest given our operations can only increment & our need to consider distances between integers.
  2. Employ a sliding window over the input, considering r to be the target value after k increments at each step & keep track of the sum of the current window to calculate the # of operations needed to reach the target.
  3. Close the sliding window from the left until it is valid after k operations.
  4. Update the max valid window encountered.
  5. Goto #2.

Python Solution

class Solution:
    def maxFrequency(self, nums: List[int], k: int) -> int:
        a, n, l, r, rsum, maxw = nums, len(nums), 0, 0, 0, 0
 
        a.sort()
        while r < n:
            target = a[r]
            rsum += target
 
            while target * (r - l + 1) - rsum > k:
                rsum -= a[l]
                l += 1
 
            maxw = max(maxw, r - l + 1)
            r += 1
 
        return maxw

 

Minimum Size Subarray Sum


Given an array of integers & a positive integer target, return the minimal length subarray whose sum is at least the target. If no such subarray exists, return 0.

Explore

t=10
5 3 2 1 2 3 6 1 9
l   r 10
  l       r 11
    l       r 14
          l   r 10
            l   r 16
              l r 10
      almin = l - r + 1
  1. almin = max int
  2. open window until sum >= target
  3. close window until sum < target updating array length min before each increment of l
  4. goto #2 while r < n

Python Solution

class Solution:
    def min_subarray_length(self, t, a):
        l, r, n, almin, rsum = 0, 0, len(a), len(a) + 1, 0
 
        while r < n:
            if rsum < t:
                rsum += a[r]
                r += 1
 
            while rsum >= t:
                almin = min(almin, r - l)
                rsum -= a[l]
                l += 1
 
        return 0 if almin == n + 1 else almin

 

Repeated DNA Sequences


Given a DNA sequence, return all 10 letter substrings that occur more than once.

Explore

A A A A A C C C C C A A A A A C C C C C C A A A A A G G G T T T
l                 r
  1. Slide the window open to the first 10 letter long substring creating the current substring along the way.
  2. While sliding the window right until the end: a. Keep track of the current 10 letter substring as the window moves, i.e. add and remove the beginning & the end of the string each time. b. Check each 10 letter substring against a set. If it is in the set, add it to the solution.
  3. Return the solution substrings as an array.

Python Solution

from collections import Counter
 
 
class Solution:
    def m(self, dna_sequence: str) -> List[str]:
        l, r, n, a, css, pss = 0, 0, len(dna_sequence), dna_sequence, "", Counter()
        soln = []
 
        if n < 10:
            return soln
 
        while r < 10:
            css += a[r]
            r += 1
        pss[css] += 1
 
        while r < n:
            css += a[r]
            css = css[1:]
            pss[css] += 1
 
            if pss[css] == 2:
                soln.append(css)
 
            r += 1
 
        return soln

Complexity

We slide a fixed size window over an input sequence which takes 2n operations in the worst case, the one in which the window approaches the size of the input sequence. This take O(n) time & uses a hashmap which in this fixed sized window case takes O(n) space.

 

Shortest Subarray With OR at Least K II


Given a non-negative integer array & an integer K, return the shortest subarray with bitwise OR of at least K. Return -1 if no such array exists.

Explore

        k=3        k=3         k=3
        1 1 1      1 2 2       1 2 3
        l   r      l r         l r
        -> -1        l  r        l r
                     -> 2          lr
                                 -> 1
  1. Slide open a window until the bitwise OR of its elements >= K.
  2. Update the min length subarray.
  3. Slide window closed from the left until bitwise OR < K. a. Turns out that removing the leftmost integer is impossible without tracking which bits are attributed to which integer or keeping a running sum of each bit individually.
  4. GOTO #1.

Python Solution

from collections import Counter
 
 
class Solution:
    def get_num(self, bor: dict) -> dict:
        num = 0
        for i in range(32):
            if bor[i]:
                num |= 1 << i
        return num
 
    def update_bor(self, bor: dict, a: int, u: int) -> dict:
        for i in range(32):
            if (a >> i) & 1:
                bor[i] += u
        return bor
 
    def min_subarray_or_k(self, nums: List[int], k: int) -> int:
        a, n, bor, l, r = nums, len(nums), Counter(), 0, 0
        mlsarray = n + 1
 
        # What are the edges cases?
        if n == 0:
            return -1
        if k == 0:
            return 1
 
        while r < n:
            bor = self.update_bor(bor, a[r], 1)
            r += 1
            while self.get_num(bor) >= k:
                mlsarray = min(mlsarray, r - l)
                bor = self.update_bor(bor, a[l], -1)
                l += 1
 
        return mlsarray if mlsarray < n + 1 else -1

 

3Sum


Given an integer array, return all unique triplets that sum to zero|target without replacement.

Explore

t = 2
-2 0 1 3
 c l   r
     l r
  1. Sort the input.
  2. For all i, using t - i = c, find l + r = c by closing a window over the rest of the input.

Python Solution

class Solution:
    def threeSum(self, a, t=0) -> List[List[int]]:
        a.sort()
        soln, unique, n = [], set(), len(a)
        for i, x in enumerate(a):
            c = t - x
            l, r = i + 1, n - 1
            while l < r:
                s = a[l] + a[r]
                if s < c:
                    l += 1
                elif s > c:
                    r -= 1
                else:
                    sl = [t - c, a[l], a[r]]
                    st = tuple(sorted(sl))
                    if st not in unique:
                        soln.append(sl)
                        unique.add(st)
                    l += 1
                    r -= 1
 
        return soln

 

3Sum Smaller


Given an integer array, return the number of triplets, indices i<j<k, that sum to less that the target.

Explore

  t < 2          t < 2        t < 2
  -2 0 1 3       3 0 0 0      3 3 3 3
  c  l r         0 0 0 3       -> 0
     l   r       c l r
   -> 2           -> 1

Python Solution

class Solution:
    def m(self, nums, target) -> int:
        soln, N = 0, len(nums)
        nums.sort()
 
        for i, n in enumerate(nums):
            c = target - n
            l = i + 1
            r = N - 1
            while l < r:
                if nums[l] + nums[r] < c:
                    # then all l+1...r-1 work as well
                    soln += r - l
                    l += 1
                else:
                    r -= 1
        return soln

Complexity

In the worst case the sorting operation took nlogn time, then we performed nested iteration on the input resulting in an overall O(n^2) time & using constant O(1) space.

 

3Sum Closest


Given an integer array & target, find the three integers in the input such that their sum is closest to the target. Return their sum & assume their is exactly one solution.

Explore

  t=9
  1 2 3 4 5
  1 l     r
  1. Sort the input.
  2. Iterate the input using a sliding window on the subarray following the current index to find the three sum that minimizes the difference from the target.

Python Solution

import sys
 
 
class Solution:
    def m(self, nums, target) -> int:
        N, min, soln = len(nums), sys.maxsize, 0
        nums.sort()
 
        for i, n in enumerate(nums):
            l, r = i + 1, N - 1
            while l < r:
                s = n + nums[l] + nums[r]
                d = target - s
                if abs(d) < abs(min):
                    min = d
                    soln = s
                if s < target:
                    l += 1
                else:
                    r -= 1
 
        return soln

Complexity

We sort the input in nlogn time & then perform a nested iteration using the sliding window from each index in the worst case for an O(n^2) runtime using constant, O(1), space.