Sliding Window, a popular method in programming problems, surprisingly, shares the same idea with Binary Search. In this post, we will show that Sliding Window is a 2D extension of Binary Search. We will also find that solving Sliding Window problems is nothing more than searching the 01 boundaries in 2D space.
This post is the second post of the【01Boundary】series.
- We first model the two situtations of Sliding Window, Max Window and Min Window, with the state and the binary predicate .
- Then, we talk about four popular problems (minimum length window, maximum length window, min counting, and max counting) under the model.
- After that, we derive the duality of Max Window and Min Window. Then we prove that we could transform Max Window to Min Window by negating the predicate .
- At the end of the introduction, we provide a code template for Min Window.
- In the Selected Problem section, we mainly focus on two types of problems:
- Max/Min Window Problems
- Leetcode 76 Minimum Window Substring
- Leetcode 632 Smallest Range Covering Elements from K Lists
- … and other 6 problems.
- Subarray Counting Problems
- Leetcode 1248 Count Number of Nice Subarrays
- Leetcode 1918 Kth Smallest Subarray Sum
- … and other 3 problems.
- Max/Min Window Problems
Keywords for sliding window problems:
longest/shortest subarray/substring, maximum/minimum length subarray/substring, number of such subarray.
Introduction
Min Window
Define the state $s = (i, j)$, $0 \leq i \leq j < N$.
Define the binary predicate $\text{Pred}$ as a function on the space of the state $s$.
Property 1: For all $j \in [0, N)$, There exists one and only one $i_{m} \in [0, j)$, such that:
- $\forall i \in [0, i_{m})$, $\text{Pred}(i, j) = 1$
- $\forall i \in [i_{m}, j)$, $\text{Pred}(i, j) = 0$
Property 2: For all $i \in [0, N)$, There exists one and only one $j_{m} \in [i, N)$, such that:
- $\forall j \in [i, j_{m})$, $\text{Pred}(i, j) = 0$
- $\forall j \in [j_{m}, N)$, $\text{Pred}(i, j) = 1$
1 | j = 13, im = 11 |
Take this figure as an example. The value of every $\text{Pred}(i, j)$ is shown in the figure at its position $(i, j)$. As a result of Property 1 and Property 2, we could observe the following two situations.
- If we fix $i$ and increase $j$, once $\text{Pred}(i, j)$ becomes $1$, it will remain $1$ as we continue increasing $j$.
- Similarly, If we fix $j$ and increase $i$, once $\text{Pred}(i, j)$ becomes $0$, it will remain $0$ as we continue increasing $i$.
Now consider the following two problems:
- PROBLEM1: Find $\min(j - i)$, such that $0 \leq i < j < N$ and $\text{Pred}(i, j) = 1$
- PROBLEM2: Find the number of states $s = (i, j)$, such that $0 \leq i < j < N$ and $\text{Pred}(i, j) = 1$.
For the first problem, it is equivalent to finding for every . Then we simply take the minimum of all such .
It could be observed from the figure that for each , the smallest that makes is exactly in Property 2.
- All has , all has .
- When is fixed, is the smallest possible that has .
- That is, minimizes for each , such that .
- Therefore, for each , once has reached , there is no need to increase anymore.
When we have reached , we could fix and increase , which makes smaller. From Property 1, we know that there will be some that makes .
Once we has visited , then fix and increase again. We keep doing this until we reach the final state .
We could observe that for each , every corresponding is visited when we have reached the final state. Thus, we could find for each . Then we take as the result of the problem.
For the second problem, we visit for each in the same way as the first problem. For each , we know that all have . Therefore, for each , there are states that have . We could sum for each together to get the result.
The transition of state $(i, j)$ is shown in the figure below. starts at and ends at .
1 | j = 13, im = 11 j = 13, im = 11 |
Max Window
Let us consider another situation when we negate the binary predicate $\text{Pred}$ in Property 1 and Property 2.
Define the state $s = (i, j)$, $0 \leq i \leq j < N$.
Define the binary predicate $\text{Pred}$ as a function on the space of the state $s$.
Property 1: For all $j \in [0, N)$, There exists one and only one $i_{m} \in [0, j)$, such that:
- $\forall i \in [0, i_{m})$, $\text{Pred}(i, j) = 0$
- $\forall i \in [i_{m}, j)$, $\text{Pred}(i, j) = 1$
Property 2: For all $i \in [0, N)$, There exists one and only one $j_{m} \in [i, N)$, such that:
- $\forall j \in [i, j_{m})$, $\text{Pred}(i, j) = 1$
- $\forall j \in [j_{m}, N)$, $\text{Pred}(i, j) = 0$
1 | j = 13, im = 11 |
Again, take the figure above as an example. The value of every $\text{Pred}(i, j)$ is shown in the figure at its position $(i, j)$. As a result of Property 1 and Property 2, we could observe the following two situations.
- If we fix $i$ and increase $j$, once $\text{Pred}(i, j)$ becomes $0$, it will remain $0$ as we continue increasing $j$.
- Similarly, If we fix $j$ and increase $i$, once $\text{Pred}(i, j)$ becomes $1$, it will remain $1$ as we continue increasing $i$.
Now consider the following two problems with this new :
- PROBLEM3: Find $\max(j - i)$, such that $0 \leq i < j < N$ and $\text{Pred}(i, j) = 1$
- PROBLEM4: Find the number of states $s = (i, j)$, such that $0 \leq i < j < N$ and $\text{Pred}(i, j) = 1$.
PROBLEM3 is similar to PROBLEM1. We could fix and increase until . At this point, we have . However, this time does not maximize because it does not satisfies the constraint . It is the previous point (if exists) that maximize the .
Once we have reached for some , we then fix and increase to to make again. We keep doing these two steps until we reach the final state .
In PROBLEM3, we have found a way to visit for each . Using Property 2 we could know that the number of states $s = (i, j)$ that satisfies $\text{Pred}(i, j) = 1$ is the sum of for each .
1 | j = 13, im = 11 j = 13, im = 11 |
Time Complexity
- To visit for each , both Max Window and Min Window requires you start at the initial state .
- The initial state transits to some other state and finally reaches the final state .
- In PROBLEM1 - 4, we could find that the state never moves back. That is, once the state has reach some , it will never moves to some such that , .
- Furthermore, in PROBLEM1 - 4 we could observe that the state transition is stepwise, i.e. state could only transit to its adjacent state or .
- Assume that the cost for one transition is .
- Because the state never moves back, the transition of the state happens at most times.
- Assume the cost of the binary predicate for each state is .
- The time complexity for the sliding window is .
Why the name
This algorithm is always referred to as “Sliding Window.” The state is defined on the subarray of a given array in problems. Because the function is a function of all elements in the subarray , the subarray is called “window”. We increase to expand the window, then to shrink the window. This makes the “window” “slide” over from left to right.
The Duality of Max Window and Min Window
You probably notice that the example of the Min Window is just a flip of the example of the Max Window. Furthermore, the path of the state $(i, j)$ in the Min Window is the same as the path in the Max Window.
1 | j = 13, im = 11 j = 13, im = 11 |
This indicates that Max Window problems could be transformed into Min Window problems by just simply negating the function.
After negating the function, we could have PROBLEM3 and PROBLEM4 tranformed into:
- PROBLEM3N: Find $\max(j - i)$, such that $0 \leq i < j < N$ and $\text{Pred}(i, j) = 0$
- PROBLEM4N: Find the number of states $s = (i, j)$, such that $0 \leq i < j < N$ and $\text{Pred}(i, j) = 0$.
To solve PROBLEM3N, consider how we solve PROBLEM1. For each , we find its corresponding , the minimum for this fixed such that . Obviously, for a fixed , if its corresponding has , then we must have , such that is the maximum that has . Then for each , we could derive from each if . is the maximum among all .
PROBLEM4N is much easier. The number of such that is just the number of all states minus the number of the states that have , which is solved in PROBLEM2.
Code Template
Min Window
1 | void SlidingWindow(const std::vector<int>& input) { |
Max Window
1 | void SlidingWindow(const std::vector<int>& input) { |
Combined
Thanks to the duality of Min Window and Max Window, we could use one code template to solve all sliding window problems.
1 | void SlidingWindow(const std::vector<int>& input) { |
Selected Problems
MinMax Window
This category of sliding window problems asks you to find the max length subarray or the min length subarray under some conditions.
Leetcode 76 Minimum Window Substring
Leetcode 76 Minimum Window Substring
Given two strings
s
andt
, return the minimum length substringm
ofs
such thatm
contains every character int
.s
andt
are not empty. It is guaranteed that there exists a substring ofs
that contains every character int
.
Define the predicate function Pred(i, j)
, which returns whether the substring s[i, j)
contains every character in t
.
Consider the substring ss = s[i, j)
of s
. s[i, j)
starts at index i
and ends at index j - 1
.
- If
j == i
, then the substringss
is an empty substring. An empty string cannot contain any character int
. - When
j
increases, new characters will be appended to the substring. As new characters are appended, There will be somej = jm
such thats[i, jm)
is the first subarray that contains every character int
. (This is guaranteed in the description of the problem.)For example, take
s = "ADOBECODEBANC" t = "ABC"
.- When
i = 0
, we increasej
. S[i,j)
changes as:""
,"A"
,"AD"
,"ADO"
,"ADOB"
,"ADOBE"
,"ADOBEC"
."ADOBEC"
is the first subarray that contains every character int
.
- When
If the substring
s[i, jm)
has already contained every character int
, it will remain to contain every character int
no matter how many characters are appended. Thus, Property 2 holds.If we increase
i
for some fixedj
, as the number of characters in the substrings[i, j)
decreases, there will be anim
such that the substrings[im, j)
does not contain every character int
. Thus, Property 1 holds.For example, take
s = "ADOBECODEBANC" t = "ABC"
.- When
i = 0, j = 6
, we increasei
. S[i,j)
changes as:"ADOBEC"
,"DOBEC"
."DOBEC"
is the first subarray that does not contain every character int
whenj = 6
.
- When
Let us consider the time complexity of this algorithm. As mentioned before, the time complexity of the sliding window algorithm is O(NP)
where N
is the range of i
and j
, O(P)
is the time complexity of calling Pred
.
To check whether a string a
contains all characters in b
, we could count the frequency of letters in a
and b
and compare their frequency. Counting the frequency of substring s[i, j)
could be at worst O(N)
time, where N
is the length of the string s
. Thus calling Pred
needs O(N)
time. The time complexity of this sliding window algorithm will be O(N * N) = O(N^2)
, which is not acceptable given the constraint N ~ 10^5
.
To optimize the time complexity of Pred
, we consider the following approaches:
- Maintain the letter frequency of the subarray online. This is achieved by updating the frequency when
i
orj
changes. Whenj
increases toj + 1
, add 1 to the frequency of the letters[j]
. Wheni
increases toi + 1
, decrease 1 from the frequency of the letters[i]
. - Compute the letter frequency of the string
t
,FreqT
in advance. Pred
is then implemented to compare the frequency of 52 letters ([a - z][A - Z]
) ofFreqS
andFreqT
. In this way, the time complexity ofPred
isO(52) = O(1)
.- The overall time complexity of the sliding window algorithm becomes
O(N)
, which is acceptable forN ~ 10^5
.
The following figure shows the path of (i, j)
in the sliding window algorithm. s = "ADOBECODEBANC"
, t = "ABC"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 t = A B C │minLength = 4
1 1 1 │subarray = "BANC"
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐▼┌─┬─┬─┬─┐
13│$│ │ │ │ │ │ │1│1│1│1│0│ │ │ │
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
12│C│ │ │ │ │ │ │0│ │ │ │ │ │0│$│
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┘
11│N│ │1│1│1│1│1│0│ │ │ │ │0│C│1
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┘3
10│A│ │0│ │ │ │ │ │ │ │ │0│N│1
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┘2
9│B│ │0│ │ │ │ │ │ │ │0│A│1
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┘1
8│E│ │0│ │ │ │ │ │ │0│B│1
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┘0
7│D│ │0│ │ │ │ │ │0│E│9
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┘
6│O│1│0│ │ │ │ │0│D│8
├─┼─┼─┼─┼─┼─┼─┼─┼─┘
5│C│0│ │ │ │ │0│O│7
├─┼─┼─┼─┼─┼─┼─┼─┘
4│D│0│ │ │ │0│C│6
├─┼─┼─┼─┼─┼─┼─┘
3│B│0│ │ │0│E│5
├─┼─┼─┼─┼─┼─┘
2│O│0│ │0│B│4
├─┼─┼─┼─┼─┘
1│D│0│0│O│3
├─┼─┼─┼─┘
0│A│0│D│2
└─┼─┼─┘
│A│1
└─┘
0
In the following code:
- Function
MakeFreq
computes the letter frequency oft
in advance. curFreq
is the letter frequency that we maintain online.- Function
CheckFreq
could be seen as thePred
function mentioned above.
1 | using Freq = std::array<int, 52>; |
Leetcode 632 Smallest Range Covering Elements from K Lists
Leetcode 632. Smallest Range Covering Elements from K Lists
You have
k
lists of sorted integers in non-decreasing order. Find the smallest range that includes at least one number from each of thek
lists.
Example:
Input:nums = [[4,10,15,24,26],[0,9,12,20],[5,18,22,30]]
Output:[20, 24]
Such smallest range (inclusive) must start and end with the numbers from the k
lists. This can be proved by contradiction. Assume range r = [a, b]
is the smallest range such that it includes at least one number from each of the k
lists. Assume that a
is not a number from the k
lists, then there exists a number a1
, such that a1
is a number from the k
lists and a1 > a
. Then there exists another range r1 = [a1, b]
which is smaller than range r = [a, b]
. Again, if we assume that b
is not a number from the k
lists, we could also find another smaller range r2 = [a, b2]
. Thus we have a
and b
are both numbers from the k
lists.
The problem becomes Finding two numbers i
and j
from the numbers in k
lists such that:
i < j
- each of the
k
lists has a number included in the range[i, j]
[i, j]
is the smallest such range.
We associate every number with its list’s index. Then merge the number index pairs altogether. For example, the input1
input = [[4,10,15,24,26],[0,9,12,20],[5,18,22,30]]
becomes1
2
3nks = [(0, 1), (4, 0), (5, 2), (9, 1), (10, 0), (12, 1),
(15, 0), (18, 2), (20, 1), (22, 2), (24, 0),
(26, 0), (30, 2)]
The problem is transformed into Finding two index i
, j
of the array nks
, such that:
i < j
nks[i, j]
includes elements from every lists.- the range represented by the number
nks[i]
and the numbernks[j]
is the smallest.
We could notice that this is a minimum sliding window problem.
- The empty range cannot include elements from every list.
- If
i
is fixed, asj
increases, there will be somejm
such that[i, jm]
includes elements from every list. Besides, if we continue increasingj
after reachingjm
, no matter what we append after the range[i, jm]
, it will always include elements from every list. (PROPERTY1) - If
j
is fixed, asi
increases, there will be someim
such that[im, j]
does not include elements from every list. Besides, if we continue increasingi
after reachingim
, it will never include elements from every list again. (PROPERTY2)
To solve the problem with a minimum sliding window, we need to design the Pred
function. In this problem, Pred(i, j)
checks whether the range nks[i, j]
contains elements from all k lists.
This could be done trivially by traversing all the elements in the range nks[i, j]
and computing the frequency of each list. If the frequency of each list is greater than or equal to one, then Pred(i, j) = 1
, otherwise 0
. This trivial method has a time complexity O(N + K)
, where N
is the number of elements in nks
and K
is the number of lists. The minimum sliding window algorithm has an overall complexity O(N(N+K)) ~ O(N^2 + NK)
. Given the constraint k ~ 3500
and N ~ 50 * 3500 = 175000
, O(N^2 + NK)
is not acceptable when N ~ 2 * 10^5
. We have to come up with an optimized Pred
with time complexity not greater than logarithm.
Consider the statement “at least one number from each of the k
lists.” This statement is equivalent to “the minimum frequency of numbers from each of the k
lists is greater than or equal to 1
.”
This equivalence indicates that to design Pred
, we have to design a data structure that supports the following operations online:
A
is an array.- initialize all elements in
A
as0
. - increase
A[k]
by1
. - decrease
A[k]
by1
. - return the minimum element in
A
.
Let us call this data structure FreqCounter
. FreqCounter
could be implemented by combining an array with a priority queue. The priority queue contains elements with outdated frequency. Before each query, outdated elements at the top of the priority queue will be discarded. A detailed discussion of this data structure is beyond the scope of this blog post.
1 | struct FreqCounter{ |
With the help of FreqCounter
, Pred
could be done in logarithm time. The minimum sliding window algorithm is implemented as follows:
- Before increasing
j
, we callFreqCounter::Add
to add one to the list wherenks[j]
belongs. (Line 44) Pred
is implemented by checkingFreqCounter::QueryMin > 0
. If the result ofFreqCounter::QueryMin
is greater than 0, then the range[i, j)
includes elements from every list. (Line 45)- Before increasing
i
, we callFreqCounter::Remove
to decrease one to the list wherenks[i]
belongs. (Line 51)
1 | struct NumK{ |
Leetcode 424 Longest Repeating Character Replacement
Leetcode 424 Longest Repeating Character Replacement
Given a string
s
with only uppercase English letters and an integerk
. You can change any character ins
to any other uppercase English letter. You can do this operation at mostk
times. Return the maximum length of the single letter subarray ofs
after you perform the above operation.
Consider the substring s[i, j)
. The minimum number of operations that you need to make s[i, j)
a single letter substring is j - i - MaxFreq(i, j)
, where MaxFreq(i, j)
returns the maximum frequency of the letters in the substring s[i, j)
.
For example, consider the substring
ss = "AABA"
.MaxFreq
is 3, which is the frequency ofA
because it is the largest frequency in the substring. Then the number of operations is4 - 3 = 1
, which means we have to change all other characters with lower frequency thanA
intoA
.
Now consider the changing of j - i - MaxFreq(i, j)
when we increase j
or i
.
- When we fix
i
and increasej
,j - MaxFreq(i, j)
is a non-decreasing function. The monotonicity is because when we add a new character in the substrings[i, j)
by increasingj
:MaxFreq(i, j)
could increase1
.MaxFreq(i, j)
may not change.j
always increases1
.
- When we fixe
j
and increasei
,-i - MaxFreq(i, j)
is a non-increasing function, which is due to a similar reason as above.
Therefore, if we define Pred(i, j) = (j - i - MaxFreq(i, j)) > k
, this problem becomes a minimum sliding window. We need to get the longest subarray such that Pred(i, j) = 0
, which is just PROBLEM3N.
The MaxFreq(i, j)
could be maintained by combining a priority queue with an array, which is similar to the FreqCounter
in the previous problem Leetcode 632 Smallest Range Covering Elements from K Lists.
1 | struct MaxFreq{ |
Leetcode 1156 Swap For Longest Repeated Character Substring
Given a string
s
, you can swap two characters ins
. Return the length of the longest single letter substring.
This problem is similar to Leetcode 424 Longest Repeating Character Replacement. In Leetcode 424, you can replace any character with any other character. However, in this problem, you could only replace one character with another character in s
.
As mentioned in the previous problem, the number of characters that we need to swap in the current substring is j - i - MaxFreq(i, j)
.
- Given that we could only swap two characters(swap once), we have to define the predicate as
Pred = j - i - MaxFreq(i, j) > 1
. (Line 59) - When
Pred = 0
, we then check if there are enough characters we could swap into the substring. (Lines 63 - 68)- We first find which character has the maximum frequency in the substring. (Line 63)
- Then, all other characters in the substring will be replaced with the maximum frequency character. (Line 64)
- If we could do this swap, we must have “total number of maximum frequency character in
s
“ - “number of maximum frequency character in the substring” >= “number of other characters in the substring” (Line 65 - 66).
1 | using Freq = std::array<int, 26>; |
Leetcode 727 Minimum Window Subsequence
Given strings
s1
ands2
, return the minimum contiguous substring part ofs1
so thats2
is a subsequence of the part.
We could easily observe that the substring ss1 = s1[i, j)
of s1
and the predicate Pred
= “s2
is a subsequence of ss1
.” together form a minimum sliding window.
If we fix
i
and increasej
, there will be somej = jm
, such thats2
is a subsequence ofs1[i, jm)
. If we continue increasingj
after we have reachedjm
, the substrings1[i, j), j > jm
still hass2
as its subsequence.For example,
s1 = "abcdebdde"
,s2 = "bde"
. If we fixi = 0
and increasej
, we could havejm = 5
. The substrings1[i, jm)
is"abcde"
. If we continue increasingj
at this point, no matter how many letters we append after the window,s2 = "bde"
is still a subsequence ofs1[i, j)
.If we fix
j
and increasei
, there will be somei = im
, such thats2
is not a subsequence ofs1[im, j)
. If we continue increasingi
after we have reachedim
, the substrings1[i, j), i > im
cannot haves2
as its subsequence again.For example,
s1 = "abcdebdde"
,s2 = "bde"
. If we fixj = 5
and increasei
, we could haveim = 2
. The substrings1[im, j)
is"cde"
. If we continue increasingi
at this point, no matter how many letters we discard at the front of the window,s2 = "bde"
is still not a subsequence ofs1[i, j)
.
Then we have to design the predicate Pred
= “s2
is a subsequence of ss1
.” A trivial design of the predicate is to have one pointer at the start of ss1
and another pointer at the start of s2
. The idea is shown in the code below.
1 | bool IsSubsequence(std::string_view s1, std::string_view s2) { |
However, this implementation has a time complexity of O(max(S1, S2)) = S1
, where S1
is the length of the string s1
and S2
the length of the string s2
(If s2
is a subsequence of s1
, we must have S2 <= S1
). The overall complexity of the minimum sliding window will be O(S1^2)
. Given the constraint S1 ~ 2 * 10^4
, this is not acceptable. Again, to optimize the time complexity, we have to maintain the predicate online.
While the window is expanding(i.e., while j
is increasing), maintaining the predicate is easy. We keep the pointer matchIdx_
pointing to the next unmatched character of the pattern string s2
. If we read in a new character (i.e., when j
is increasing), we compare it against s2[matchIdx_]
. If they are the same character, we increase matchIdx_
. If matchIdx_ == s2.size()
, we know that the current substring s1[i, j)
has s2
as its subsequence.
However, it can be hard to maintain the predicate while the window is shrinking (i.e., while i
is increasing). The solution is: we do not shrink the window step by step. Instead, when j
is fixed, we could jump to im
directly!
Consider the substring s1[i, jm)
. s1[jm - 1]
must be the last character of s2
. We can prove it by contradiction. Given the substring s1[i0, jm)
, where j = jm
is the smallest j
for i0
, such that s2
is a subsequence of s1[i0, jm)
. Assume that s1[jm - 1] != s2[S2 - 1]
. Because s2
is a subsequence of s1[i0, jm)
and s1[jm - 1] != s2[S2 - 1]
, there must be some j0 < jm - 1
such that s1[j0] == s2[S2 - 1]
. Then we could have another substring s1[i0, j0 + 1)
for this i0
, that has s2
as its subsequence. This substring s1[i0, j0 + 1)
has its ending j0 + 1 < jm
, which contradicts the assumption that jm
is the smallest such j
. Thus, we proved that s2[jm - 1]
must be the last character of s2
by contradiction.
For example, if we have
s1[i, jm) = "abcde", s2 = "bde"
. Then we could start with the last character'e'
ofs1[i, jm)
and match backward.sss1 = bcde"
in this case.
This idea is shown in the following code.
Given that s1[jm - 1]
must be the last character of s2
, we could match s2
against s1
backward to find the smallest substring sss1
ending at jm
of the substring s1[i, jm)
.
1 | int FindSmallestSubstringReversely(std::string_view ss1, std::string_view s2) { |
Then the im
for this j
is simply the index of second character n of sss1
in s1
. Because this i = im
must be the first i
for this fixed j
such that s1[im, j)
starts not taking s2
as its subsequence.
Notice that after we move i
to im
, we have to recompute the matchIdx_
. We have to recompute how many characters are matched in the substring s1[im, j)
.
For example, if we have
s1 = "abcdebdde", s1[0, 5) = "abcde", s2 = "bde"
. Thensss1 = "bcde"
, we moveim
to the second character'c'
insss1
, which hasindex = 2
ins1
.
Then the minimum sliding window algorithm goes as follows:
- Before increasing
j
, we matchs1[j]
againsts2[matchIdx_]
to update the predicatess
. (Line 73) - If the current window has
s1
as its subsequence, we jump directly to theim
of thisj
. (Line 75)
1 | struct Subsequence{ |
Other Problems
Leetcode 904 Fruit Into Baskets
This problem asks you to find the longest substring such that there is at most two types of elements in the substring. This is a maximum sliding window problem. The frequency of the elements in the current subarray could be maintained online easily.
1 | struct Baskets{ |
1208. Get Equal Substrings Within Budget
This problem asks you to find the maximum length substring such that the sum of the elements in the substring is not greater than maxCost
.
1 | struct Budget{ |
Leetcode 1234 Replace the Substring for Balanced String
Given a string
s
containing'Q'
,'W'
,'E'
and'R'
. You can replace one substring ofs
with any other substring.s
is said to be balance if all'Q'
,'W'
,'E'
and'R'
appearlen(s) / 4
times. Return the minimum length of such substring to makes
balance.
Whether replacing a substring can balance the s
or not depends on the frequencies of 'Q'
, 'W'
,'E'
, and 'R'
out of the substring. The frequency of 'Q'
out of the substring is the frequency of 'Q'
in the string minus the frequency of 'Q'
in the subtring.
After computing the frequencies of 4 letters out of the substring, we then check these frequencies.
- If the frequency of one letter is greater than
len(s) / 4
, we could not balances
because we cannot decrease the frequency of the letter out of the substring by replacing the substring. - If the frequency of one letter is equal to
len(s) / 4
, do nothing. - If the frequency of one letter is less than
len(s) / 4
, assign some slots in the substring to make the frequency equal tolen(s) / 4
- After assigning all the slots (the number of slots is equal to the length of the substring), there should not be any empty slot.
Notice that if replacing the substring s[i, j)
could balance s
, then after expanding the substring (increasing j
), the new substring s[i, j1), j1 > j
still balances s
. This is because we could just keep the new letters appended as they are.
If replacing the substring s[i, j)
could not balance s
, then after shrinking the substring (increasing i
), the new substring s[i1, j), i1 > i
could not balance s
. This could be proved by contradiction. Given that the substring s[i, j)
could not balance s
. Assume that after shrinking the substring (increasing i
), the new substring s[i1, j), i1 > i
could balance s
. Then we could just prepend all the letters that we discarded while shrinking and keep them as they are. Then after prepending all discarded letters, s[i, j)
must balance s
. This contradicts the assumption and is hence proved.
These two properties indicate that the substring s[i, j)
with the Pred
= “whether replacing the substring could make the original string balance or not” together form a sliding window.
Notice that Lines 91 - 92 take care of a corner case. In most cases, we do not care about empty substrings. However, in this problem, empty substrings could be the solution. We need to check the exit of the loop (Lines 87 - 90) is due to i >= j
or !fc.CanBalance()
. If the exit is due to i == j
, we have to check whether the empty substring could balance the original string or not.
1 | using Freq = std::array<int, 4>; |
Counting Window
Leetcode 713 Subarray Product Less Than K
Leetcode 713 Subarray Product Less Than K
Given an array
nums
with all elements greater than0
. Find the number of subarrays such that the product of elements in the subarray is less thank
.
Consider the subarray nums[i, j)
. Because every element in the subarray is greater than 0
, we have:
- If we fix
i
and increasej
, then the product of elements in the subarray must increase. There will be somej = jm
that makes the product of the subarray no longer less thank
. - If we fix
j
and increasei
, then the product of elements in the subarray must decrease. There will be somei = im
that makes the product of the subarray less thank
.
This indicates that the subarray and the product of its elements form a maximum sliding window. Because of the duality of maximum sliding window and minimum sliding window, we negate the original condition and transform it into “the product of elements in the subarray is greater than or equal to k
.” We call this condition Pred
.
We count the number of subarrays that have Pred(i, j) = 1
as in PROBLEM2. The total number of non-empty subarrays is (n * (n - 1)) / 2
, where n
is the size of nums
. Then the number of subarrays that have Pred(i, j) = 0
could be derived by subtracting the number of Pred(i, j) = 1
out of the total number of non-empty subarrays.
1 | class Solution { |
Leetcode 1248 Count Number of Nice Subarrays
Given an array with positive integers, return the longest subarray such that it contains exactly
k
odd integers.
Consider the following problem Leetcode 1248 SUBPROBLEM:
Given an array with positive integers, return the longest subarray such that it contains less than
k
odd integers.
This problem could be solved easily by a maximum sliding window. It is the same problem as PROBLEM3N.
Just maintain a variable numberOfOdds
counting the number of odds in the subarray. When numberOfOdds >= k
, shrink the window.
1 | int NumberOfSubarraysLessThanK(const std::vector<int>& nums, int k) { |
After solving Leetcode 1248 SUBPROBLEM, the original problem could be solved by “the number of arrays have less than (k + 1) odds” minus “the number of arrays have less than (k) odds”
1 | class Solution { |
Leetcode 1918 Kth Smallest Subarray Sum
Leetcode 1918 Kth Smallest Subarray Sum
Given a positive integer array nums of length n and an integer k, return the kth smallest subarray sum.
Consider the following problem Leetcode 1918 Kth Smallest Subarray Sum SUBPROBLEM:
Given the function
NumberOfElementsLessThan(N)
, which returns the number of elements in the array that is less than or equal toN
. The maximum element of the array ismax
, the minimum element of the array ismin
, and return thek
th smallest element.
Let us take the array [1, 2, 3, 3, 4, 6]
as an example.
- There are
1
element less than or equal to the 1st smallest element —1
. - There are
2
elements less than or equal to the 2nd smallest element —2
. - There are
4
elements less than or equal to the 3rd smallest element —3
. - There are
4
elements less than or equal to the 4th smallest element —3
. (Notice that there are two3
s in the array.) - There are
5
elements less than or equal to the 5th smallest element —4
. - There are
6
elements less than or equal to the 5th smallest element —6
.
If we want to find the 3rd smallest element, we find the first element such that there are greater or equal 3
elements that are less than or equal to it.
If we want to find the 4th smallest element, we find the first element such that there are greater or equal 4
elements that is less than or equal to it. This will lead us to the first 3
in the array, but it does not matter because the 3rd smallest element and the 4th smallest element are the same.
Because there could be some elements that have the same value, the k
th element does not necessarily have k
elements less than or equal to it. (For example, the 3rd element 3
in the array has 4 elements less than or equal to it.)
Thus, if we want to find the k
th smallest element, we could find the first element such that there are greater or equal k
elements that are less than or equal to it.
Notice that the number of elements less than or equal to the k
th smallest element is strictly increasing w.r.t k
. Therefore, Leetcode 1918 Kth Smallest Subarray Sum SUBPROBLEM could be solved by binary search on the range [min, max + 1)
. The predicate is designed to be:1
2
3int Pred(int n) {
return NumberOfElementsLessThan(n) < k;
}
Then the binary search will stop at the first element such that NumberOfElementsLessThan(n) >= k
. This element is the k
th smallest element.
Now let us get back to the original problem. The original problem asks you to find the k
th smallest subarray sum. We know that by sliding window, we could compute the number of subarrays such that its sum is less than or equal to a certain number N
. By Leetcode 1918 Kth Smallest Subarray Sum SUBPROBLEM, the k
th smallest subarray sum in the range [0, S)
is the first number such that it has greater than or equal to k
subarray sums that are less than or equal to it. S
is the largest sum, the sum of all elements in the array. We could use binary search to find the k
th element as in Leetcode 1918 Kth Smallest Subarray Sum SUBPROBLEM.
1 | int NumberOfSubarraysSumLessThanN(const std::vector<int>& nums, int N) { |
Other Problems
LeetCode 1358 Number of Substrings Containing All Three Characters
Given string
s
with onlya
,b
, andc
. Return the number of substrings such that it contains at least one occurrence of all these characters a, b and c.
This is a minimum sliding window, PROBLEM2.
1 | struct FreqCounter{ |