LeetCode Sliding Window Problems
Introduction to sliding window pattern
· 6 min read
What is the sliding window algorithm? #
Let’s first demonstrate what is the sliding window approach and why we use it by solving this problem: Minimum Size Subarray Sum - LeetCode
Problem description #
Given an array of positive integers nums and a positive integer target, return the minimal length of a subarray whose sum is greater than or equal to target. If there is no such subarray, return 0 instead.
Example 1:
Input: target = 7, nums = [2,3,1,2,4,3] Output: 2 Explanation: The subarray [4,3] has the minimal length under the problem constraint.
Example 2:
Input: target = 4, nums = [1,4,4] Output: 1
Example 3:
Input: target = 11, nums = [1,1,1,1,1,1,1,1] Output: 0
Constraints:
1 <= target <= 1091 <= nums.length <= 1051 <= nums[i] <= 104
A naive solution #
A brute force solution could be trying every possible subarray and calculating the sum of the numbers in the subarray. If the sum is larger than target, we update the minLength if the length of the subarray is less than the current minimum.
int minLength = Integer.MAX_VALUE;
for (int i = 0; i < nums.length; ++i) {
int sum = 0;
for (int j = i; j < nums.length; ++j) {
sum += nums[j];
if (sum > target) {
minLength = Math.min(minLength, j - i + 1);
}
break;
}
}
The problem is that there are a lot of duplicated calculations due to many subarrays sharing a common overlap.
If we can somehow memorize the summation for the overlap, we no longer need to calculate the sum of the overlapping area twice.
Sliding window approach #
The idea is to use a sliding window to keep track of the subarray we are visiting:
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int res = Integer.MAX_VALUE, sum = 0, windowStart = 0;
for (int windowEnd = 0; windowEnd < nums.length; ++windowEnd) {
sum += nums[windowEnd];
while (sum >= target) {
res = Math.min(res, windowEnd - windowStart + 1);
sum -= nums[windowStart++];
}
}
return res == Integer.MAX_VALUE ? 0 : res;
}
}
- Each iteration, we enlarge the window size by incrementing
windowEndby 1, and add the number intosum - If
sum > target, which means we have a valid subarray (window), we updateresif possible - If
sum > target, the subarray already satisfies the requirement, in order to get the smallest such subarray, we shrink the window size by incrementing the left boundary —windowStart
Compared with the $O(n^2)$ native solution, we achieve $O(n)$ by leveraging the sliding window algorithm, which always keeps track of a valid window that satisfies the requirements.
Problems requiring a map #
A sliding window can be easily integrated with other data structures like a map, to keep track of more sophisticated information within the window. Let’s take a look at 567. Permutation in String
Problem statement #
Given two strings s1 and s2, return true if s2 contains a permutation of s1, or false otherwise.
In other words, return true if one of s1’s permutations is the substring of s2.
Example 1:
Input: s1 = “ab”, s2 = “eidbaooo” Output: true Explanation: s2 contains one permutation of s1 (“ba”).
Example 2:
Input: s1 = “ab”, s2 = “eidboaoo” Output: false
Constraints:
1 <= s1.length, s2.length <= 104s1ands2consist of lowercase English letters.
Solution #
The idea is very similar:
- We keep track of a window by recording the char frequencies in a map.
- Each iteration:
- Enlarge the current window by moving the
windowEndpointer - Update the map to reflect the newly-added char
- If all chars from
s1are stored in the current window, returntrue - If the size of our window is larger than
s1.length(), then we need to shrink it by incrementing thewindowStartpointer and updating the map
- Enlarge the current window by moving the
- If the iteration ends, then
s2is not a permutation ofs1, returnfalse
class Solution {
public boolean checkInclusion(String s1, String s2) {
Map<Character, Integer> charCntMap = new HashMap<>();
// init, storing the frequencies of chars in s1 into a map
for (char ch : s1.toCharArray()) {
charCntMap.put(ch, charCntMap.getOrDefault(ch, 0) + 1);
}
int windowStart = 0, cnt = charCntMap.size();
for (int windowEnd = 0; windowEnd < s2.length(); ++windowEnd) {
char ch = s2.charAt(windowEnd);
// update map
if (charCntMap.containsKey(ch)) {
charCntMap.put(ch, charCntMap.get(ch) - 1);
if (charCntMap.get(ch) == 0) {
--cnt;
}
}
// the window is larger than s1, shrink the window and update the map
if (windowEnd - windowStart + 1 > s1.length()) {
ch = s2.charAt(windowStart++);
if (charCntMap.containsKey(ch)) {
charCntMap.put(ch, charCntMap.get(ch) + 1);
if (charCntMap.get(ch) == 1) {
++cnt;
}
}
}
// all chars are present in current window, return true
if (cnt == 0) {
return true;
}
}
return false;
}
}
Similar problems:
Problems using a set #
In this problem—3. Longest Substring Without Repeating Characters, instead of using a map, a set data structure is utilized to record “presences” of chars in the sliding window.
Problem description #
Given a string s, find the length of the longest substring without duplicate characters.
Example 1:
Input: s = “abcabcbb”
Output: 3
Explanation: The answer is “abc”, with the length of 3. Note that "bca" and "cab" are also correct answers.
Example 2:
Input: s = “bbbbb” Output: 1 Explanation: The answer is “b”, with the length of 1.
Example 3:
Input: s = “pwwkew” Output: 3 Explanation: The answer is “wke”, with the length of 3. Notice that the answer must be a substring, “pwke” is a subsequence and not a substring.
Constraints:
0 <= s.length <= 5 * 104sconsists of English letters, digits, symbols and spaces.
Solution #
The idea is not too much different from the previous one. Instead of using a map to store char frequencies, in this problem we are only interested in the presence of chars.
At each iteration:
- We first enlarge the window by 1 as usual.
- If the char denoted by
windowEndis already present in the set, we need to shrink the window until it is valid, i.e., the window contains no duplicated char:- To do so, we shrink the window by moving the left boundary until the same char added in a previous iteration is excluded
- After the exclusion, we are sure the window meets with the requirement, we then update the maximal length of a valid window we have met.
class Solution {
public int lengthOfLongestSubstring(String s) {
int res = 0, windowStart = 0;
Set<Character> set = new HashSet<>();
for (int windowEnd = 0; windowEnd < s.length(); ++windowEnd) {
char ch = s.charAt(windowEnd);
// shrink the sliding window until it is legal
while (set.contains(ch)) {
set.remove(s.charAt(windowStart++));
}
set.add(ch);
// the window is valid now, update res
res = Math.Max (res, windowEnd - windowStart + 1);
}
return res;
}
}
Recap #
The sliding window algorithm is based on iteration, at each iteration, we:
- First, move the right boundary of the sliding window forward by 1
- Update data, no matter it is the summation of elements in the window, or is the frequencies of chars
- Shrink the window if it is invalid by moving the left boundary until the requirement is met. Update any necessary data during the process.