滑动窗口更像是一种technique而不是一种algorithm,如果一道题暴力解法需要O(n^2)orO(n^3),那么滑动窗口可以将time complexity降低到O(n)
可以用滑动窗口解决的问题类型:连续最小子数组/连续最大子数组
滑动窗口只能用于数组都是正数的情况,因为数组中都是正数,所以sum += nums[end];sum只会增大,不会减少(这也算是一种单调性吧),而对于 sum -= nums[start];sum只会减小,而不会增大,因此确保了滑窗的正确性,如果有负数的话,sum += nums[end];可能会导致sum减小,而这会导致滑窗错误,如 :target = 7, nums = [2,-10,4,3]
经典题目:
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int left = 0;
int right = 0;
int res = nums.length + 1;
int sum = 0;
while(right < nums.length) {
sum += nums[right];
while(sum >= target) {
res = Math.min(res, right - left + 1);
sum -= nums[left];
left++;
}
right++;
}
return res == nums.length + 1 ? 0 : res;
}
}
- 904. Fruit Into Baskets
- 76. Minimum Window Substring 76题一道经典hard题:需要新建很多变量,首先要新建target的map,然后还要新建遍历数组的map,之后讲两个map然后比对
- 3. Longest Substring Without Repeating Charact
滑动窗口的大致逻辑框架,伪代码如下:
int left =0,right = 0;
while (right < s.size()){
//增大窗口
window.add(s[right]);
while (window needs shrink){
//缩小窗口
window.remove (s[left]);
left ++;
}
right++;
}
- 取滑动窗口长度的时候,要看left和right是不是左边界 / 左边界 + 1,还是右边界 / 右边界 + 1,一般把left++放最后和把right++放最后正好是左边界和右边界
- 做滑动窗口题目的时候可以用一个变量count来记录当前有多少个数满足条件,这个出现在了76题和3题
424. Longest Repeating Character Replacement
You are given a string s and an integer k. You can choose any character of the string and change it to any other uppercase English character. You can perform this operation at most k times.
Return the length of the longest substring containing the same letter you can get after performing the above operations.
Example 1:
Input: s = "ABAB", k = 2
Output: 4
Explanation: Replace the two 'A's with two 'B's or vice versa.
Example 2:
Input: s = "AABABBA", k = 1
Output: 4
Explanation: Replace the one 'A' in the middle with 'B' and form "AABBBBA".
The substring "BBBB" has the longest repeating letters, which is 4.
这道题当时第一反应是用滑动窗口,但是没有想明白思路,之前想的是用一个变量表示当前要遍历的char和这个char出现的最大次数,然后就绕晕了。后来发现可以不去考虑当前的char,直接用一个变量表示当前滑动窗口出现次数最多的char,然后每次更新这个maxcount
第二次做还没做出来,反思一下原因,没搞懂sliding window中window的含义,扩大window直到k全部被用完,缩小window的临界值就是如果window的长度 > 这个window中frequency最大的那个字母 + k,就向右移动左边界。maxcount用来维护当前窗口中出现频率最大的字母
第四次做,总结一下,maxcount 不是窗口内出现次数最多的字母,而是历史窗口中出现次数最多字母的次数。因为找到一个满足条件的窗口之后,接下来只需要去找一个更大的窗口, 之前找到的那个窗口的大小 = right - left = maxn+ k,所以在k固定不变的前提下,要找更大的那个窗口,也就等价于找能够使得maxn增大的新的窗口。
right - left在整个过程是非递减的。只要right 的值加进去不满足条件,left和right就一起右滑,因为长度小于right - left的区间就没必要考虑了,所以right - left一直保持为当前的最大值
class Solution {
public int characterReplacement(String s, int k) {
int left = 0;
int right = 0;
int result = 0;
int[] freq = new int[26];
int maxcount = 0; // the number that appears most frequently in the sliding window
while (right < s.length()) {
char rightChar = s.charAt(right);
freq[rightChar - 'A']++;
maxcount = Math.max(maxcount, freq[rightChar - 'A']);
while(right - left + 1 > maxcount + k) {
char leftChar = s.charAt(left);
freq[leftChar - 'A']--;
left++;
}
result = Math.max(result, right - left + 1);
right++;
}
return result;
}
}
713. Subarray Product Less Than K
Given an array of integers nums and an integer k, return the number of contiguous subarrays where the product of all the elements in the subarray is strictly less than k.
Example 1:
Input: nums = [10,5,2,6], k = 100
Output: 8
Explanation: The 8 subarrays that have product less than 100 are:
[10], [5], [2], [6], [10, 5], [5, 2], [2, 6], [5, 2, 6]
Note that [10, 5, 2] is not included as the product of 100 is not strictly less than k.
Example 2:
Input: nums = [1,2,3], k = 0
Output: 0
同样也利用了滑动窗口来解决问题,最重要的是res += right - left + 1;这一步,就是right-left+1是以右端点元素为末尾元素的子数组的个数(区间多长,含末尾元素的子数组就有多少个),所有元素做队尾的子数组数之和就是符合条件子数组的个数。比如例子[10,5,2,6]以10做队尾的子数组[10],以5做队尾的子数组[10,5],[5],以2作队尾 [5,2],[2],以此类推。
public int numSubarrayProductLessThanK(int[] nums, int k) {
if(k == 0) return 0;
int left = 0;
int right = 0;
int res = 0;
int product = 1;
while(right < nums.length) {
product = product * nums[right];
while(left <= right && product >= k) {
product = product / nums[left];
left++;
}
res += right - left + 1;
right++;
}
return res;
}
567. Permutation in String
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
解法:这道题是一道窗口固定的题,最开始做的时候还是按照滑动窗口的方式去解决。但其实是fixed size。 然后这道题可以通过维护一个变量也就是matches来判断是否相同,不用去直接比较两个array,这样时间复杂度会低些
class Solution {
public boolean checkInclusion(String s1, String s2) {
if(s2.length() < s1.length()) return false;
int n = s1.length();
int[] map = new int[26];
int matches = 26;
int[] count = new int[26];
for(int i = 0; i < n; i++) {
map[s1.charAt(i) - 'a']++;
count[s2.charAt(i) - 'a']++;
}
for(int i = 0; i < 26; i++) {
if (count[i] != map[i]) {
matches--;
}
}
if (matches == 26) return true;
for(int i = 0; i < s2.length() - n; i++) {
int right = i + n;
int left = i;
int indexR = s2.charAt(right) - 'a';
int indexL = s2.charAt(left) - 'a';
if(count[indexR] == map[indexR]) {
matches--;
}
if(count[indexL] == map[indexL]) {
matches--;
}
count[indexR]++;
count[indexL]--;
if(count[indexR] == map[indexR]) {
matches++;
}
if(count[indexL] == map[indexL]) {
matches++;
}
if(matches == 26) {
return true;
}
}
return false;
}
}
-
串联所有单词的子串
-
滑动窗口最大值
-
最小区间