数组篇刷题模板总结三——滑动窗口
滑动窗口模板
public void slidingWindow(string s, string target) {
HashMap<Character, Integer> need = new HashMap<>();
HashMap<Character, Integer> window = new HashMap<>();
for (char c : t.toCharArray()) {
need.put(c, need.getOrDefault(c, 0) + 1);
}
int left = 0, right = 0;
int valid = 0;
while (right < s.length()) {
// c 是将移入窗口的字符
char c = s[right];
// 右移窗口
right++;
// 进行窗口内数据的一系列更新
...
/*** debug 输出的位置 ***/
printf("window: [%d, %d)\n", left, right);
/********************/
// 判断左侧窗口是否要收缩
while (window needs shrink) {
// d 是将移出窗口的字符
char d = s[left];
// 左移窗口
left++;
// 进行窗口内数据的一系列更新
...
}
}
}
算法伪代码
1、在字符串 S
中使用双指针中的左右指针技巧,初始化 left = right = 0
,把索引左闭右开区间 [left, right)
称为一个窗口。
2、先不断地增加 right
指针扩大窗口 [left, right)
,直到窗口中的字符串符合要求(包含了 T
中的所有字符)。
3、此时,停止增加 right
,转而不断增加 left
指针缩小窗口 [left, right)
,直到窗口中的字符串不再符合要求(不包含 T
中的所有字符了)。同时,每次增加 left
,都要更新一轮结果。
4、重复第 2 和第 3 步,直到 right
到达字符串 S
的尽头。
下面贴一些经典滑动窗口的例题。
1、最小覆盖子串(此题作为模板演示)
代码实现
public String minWindow(String s, String t) {
// 首先定义两个哈希表,need存储target里面每一个字符和出现的次数
HashMap<Character, Integer> need = new HashMap<>();
HashMap<Character, Integer> window = new HashMap<>();
// 初始化need哈希表
for (char c : t.toCharArray()) {
need.put(c, need.getOrDefault(c, 0) + 1);
}
// 定义左右指针
int left = 0, right = 0;
// 定义valid记录符合情况的字符串的数量
int valid = 0;
// 记录最小覆盖字串的起始索引及长度
int start = 0, len = Integer.MAX_VALUE;
while (right < s.length()) {
char c = s.charAt(right);
right++;
// 判断取出的字符是否在字串中
if (need.containsKey(c)) {
window.put(c, window.getOrDefault(c,0) + 1);
if (window.get(c).equals(need.get(c))) {
valid++;
}
}
// 判断是否需要收缩(已经找到合适的覆盖串)
while (valid == need.size()) {
if (right - left < len) {
start = left;
len = right - left;
}
char c1 = s.charAt(left);
left++;
if (need.containsKey(c1)) {
if (window.get(c1).equals(need.get(c1))) {
valid--;
}
window.put(c1, window.getOrDefault(c1, 0) - 1);
}
}
}
return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len);
}
2、最长无重复子串
题目描述
解题思路
其实本题相比于普通的字符串匹配相关的滑动窗口问题更加简单,因为不需要need哈希表,只需要一个滑动窗口即可。众所周知,滑动窗口最重要的地方就是确定收缩窗口的时机,在本题中,由于每个字符只允许出现一次,那么显而易见,当一个字符出现次数超过1时,则需要收缩左窗口。对了,由于while循环中判断条件是含有超过出现次数大于1的字符,所以只有在收缩窗口结束之后才应该更新结果。注意这两个点,就很容易写出程序,细节决定成败。
代码实现
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> window = new HashMap<>();
int left = 0;
int right = 0;
int result = 0;
while (right < s.length()) {
char ch = s.charAt(right);
right++;
window.put(ch, window.getOrDefault(ch, 0) + 1);
while (window.get(ch) > 1) {
char ch1 = s.charAt(left);
left++;
window.put(ch1, window.getOrDefault(ch1, 0) - 1);
}
result = Math.max(result, right - left);
}
return result;
}
3、长度最小子数组
题目描述
由于思路更加简单,所以直接写代码。
代码实现
public int minSubArrayLen(int s, int[] nums) {
int left = 0;
int right = 0;
int len = Integer.MAX_VALUE;
int sum = 0;
while (right < nums.length) {
sum += nums[right];
right++;
while (sum >= s) {
len = Math.min(len, right - left);
sum -= nums[left++];
}
}
return len == Integer.MAX_VALUE ? 0 : len;
}
下面贴一张关于索引和数组长度的辨析,随时复查。
滑动窗口到此结束~接下来就是征战LeetCode(ノ´▽`)ノ♪