滑动窗口题目模板
step1:根据题意维护变量
- 和 Sum
- 最大长度 max_len,最小长度 min_len
- 不重复 hashmap = {}
step2: 窗口的开始位置start和结束位置end
step3: 根据条件写判断语句,维护step1中的变量
step4: 根据题目要求,从中选择一种方法套用
- 选择一:
窗口长度固定
,如果窗口长度达到限定的长度:- 1.更新step1中的相关变量
- 2.窗口左边位置start向前移动 1,保证end向右移动时窗口长度保持不变
- 选择二:
窗口长度不固定
,则 while 窗口条件不符合:- 1.更新step1中的相关变量
- 2.不断移动start,直到窗口条件符合
step5:返回答案
3. 无重复字符的最长子串
解题思路:题目中提到不重复字符串,所以引入哈希表来维护窗口
思路一:key存储元素,value存储出现的次数
这种方式是用过存储key出现的次数来维护一个窗口:
- end - start + 1 == window.size() 说明 [start,end] 内的元素没有重复的;
- end - start + 1 >= window.size() 说明 [start,end] 内
出现了重复元素
,此时需要做处理。
public int lengthOfLongestSubstring(String s) {
int start = 0,maxLen = 0;
Map<Character,Integer> window = new HashMap<>();
for (int end = 0; end < s.length(); end++) {
char c = s.charAt(end);
window.put(c,window.getOrDefault(c,0)+1);
/*
1.end - start + 1 == window.size() 说明 [start,end] 内的元素没有重复的
2.end - start + 1 >= window.size() 说明 [start,end] 内出现了重复法元素
*/
if (end - start + 1 == window.size()) {
maxLen = Math.max(maxLen,window.size());
}
while (end - start + 1 > window.size()) {
char temp = s.charAt(start);
window.put(temp,window.get(temp) - 1);
if (window.get(temp) == 0) {
window.remove(temp);
}
start++;
}
}
return maxLen;
}
思路二:key存储元素,value存储该元素最后出现的下标(推荐使用)
这种方式是用过存储key最后出现的下标来维护一个窗口
public int lengthOfLongestSubstring(String s) {
int start = 0,maxLen = 0;
Map<Character,Integer> window = new HashMap<>();
for (int end = 0; end < s.length(); end++) {
char c = s.charAt(end);
if (window.containsKey(c)) {
start = Math.max(window.get(c) + 1,start);
}
maxLen = Math.max(maxLen,end - start + 1);
window.put(c,end);
}
return maxLen;
}
大家可能会疑惑为什么要写成 start = Math.max(window.get(c) + 1,start);
,而不写成 start = window.get(c) + 1
,下面我们以 "abba"
为例来分析一下,并且假设 start = window.get(c) + 1
:
- 步骤一:首先map中会存入 a : 0,b : 1;
- 步骤二:遍历到第二个 b 的时候,start = 1 + 1 = 2;
- 步骤三:遍历到第二个 a 的时候,start = 0 + 1 = 1(但是在
步骤二
的时候 start 已经为 2 了,问题就出在这里),此时继续往下的话 maxLen = 3 - 1 + 1 = 3,而不是正确答案 2 了。
如果是 start = Math.max(window.get(c) + 1,start);
,那么在第三步的时候,start = Math.max(1,2) = 2,则结果为 maxLen = 3 - 2 + 1 = 2,此时是正确结果。
1695. 删除子数组的最大得分
思路一:key存储元素,value存储出现的次数
这种方式是用过存储key出现的次数来维护一个窗口
public int maximumUniqueSubarray(int[] nums) {
int start = 0,sum = 0,maxVal = Integer.MIN_VALUE;
Map<Integer,Integer> window = new HashMap<>();
for (int end = 0; end < nums.length; end++) {
int temp = nums[end];
sum += temp;
window.put(temp,window.getOrDefault(temp,0) + 1);
if (end - start + 1 == window.size()) {
maxVal = Math.max(maxVal,sum);
}
while (end - start + 1 > window.size()) {
window.put(nums[start],window.get(nums[start]) - 1);
if (window.get(nums[start]) == 0) {
window.remove(nums[start]);
}
sum -= nums[start];
start++;
}
}
return maxVal;
}
思路二:key存储元素,value存储该元素最后出现的下标(推荐使用)
这种方式是用过存储key最后出现的下标来维护一个窗口
public int maximumUniqueSubarray(int[] nums) {
int start = 0,sum = 0,maxVal = Integer.MIN_VALUE;
Map<Integer,Integer> window = new HashMap<>();
for (int end = 0; end < nums.length; end++) {
if (window.containsKey(nums[end])) {
int temp = start;
start = Math.max(window.get(nums[end]) + 1,start);
while (temp != start) {
sum -= nums[temp++];
}
}
sum += nums[end];
maxVal = Math.max(maxVal,sum);
window.put(nums[end],end);
}
return maxVal;
}
为什么要写成 start = Math.max(window.get(nums[end]) +1,start);
,而不是 start = window.get(nums[end]) +1;
,之前已经讲过了。
438. 找到字符串中所有字母异位词(重要)
这里利用数组结合双指针来维护一个窗口,非常巧妙,需要多加理解。
public List<Integer> findAnagrams(String s, String p) {
int start = 0,end = 0;
List<Integer> result = new ArrayList<>();
int[] count = new int[26];
for (int i = 0; i < p.length(); i++) {
count[p.charAt(i) - 'a']++;
}
while (end < s.length()) {
if (count[s.charAt(end) - 'a'] > 0) {
count[s.charAt(end++) - 'a']--;
if (end - start == p.length()) {
result.add(start);
}
} else {
count[s.charAt(start++) - 'a']++;
}
}
return result;
}
567. 字符串的排列
本题思路和上一题相同。
public boolean checkInclusion(String s1, String s2) {
int[] count = new int[128];
for (char c : s1.toCharArray()) {
count[c]++;
}
int start = 0,end = 0;
while (end < s2.length()) {
if (count[s2.charAt(end)] > 0) {
count[s2.charAt(end++)]--;
if (end - start == s1.length()) {
return true;
}
} else {
count[s2.charAt(start++)]++;
}
}
return false;
}
其它题目
209. 长度最小的子数组
解题思路:通过滑动窗口算法找到连续子数组的和 >=target的最小长度
public int minSubArrayLen(int target, int[] nums) {
int start = 0,sum = 0,minLen = Integer.MAX_VALUE;
for (int end = 0; end < nums.length; end++) {
sum += nums[end];
while (sum >= target) {
minLen = Math.min(end - start + 1,minLen);
sum -= nums[start++];
}
}
return minLen == Integer.MAX_VALUE ? 0 : minLen;
}
643. 子数组最大平均数 I
解题思路:通过滑动窗口算法找到长度为k的连续子数组的最大和,再除以k,得到结果
public double findMaxAverage(int[] nums, int k) {
int max = Integer.MIN_VALUE;
int sum = 0;
int start = 0;
for (int end = 0; end < nums.length; end++) {
sum += nums[end];
if (end - start + 1 == k) {
max = Math.max(sum,max);
sum -= nums[start++];
}
}
return 1.0 * max / k;
}