滑动窗口大多用于处理连续子数组问题,然后得到相应的长度。
例题:长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组。如果不存在符合条件的连续子数组,返回 0。
思路:使用滑动窗口i
在左边,j
在右边,明确意义[i,j]
为滑动窗口(闭区间)。如果滑动窗口内的和小于s,则j++
来扩大窗口范围。否则说明达到题目要求,记录此时的长度,然后i++
,使窗口缩小。重复以上步骤直到i到达数组末尾,注意不要让j越界。
时间复杂度:O(n)
class Solution {
public int minSubArrayLen(int s, int[] nums) {
int tmp = 0;
for(int n:nums){
tmp+=n;
}
if(tmp<s) return 0; //假如不存在,返回0
int i = 0;
int j = 0;
int sum = nums[0];
int res = Integer.MAX_VALUE;
while(i<nums.length){
if(sum<s){ //和不够大
if(j<nums.length-1){ //当j未越界,移动j
sum+=nums[++j];
}else{ //如果越界了,移动i
sum-=nums[i++];
}
}else{
res = Math.min(res,j-i+1);
sum-=nums[i++];
}
}
return res;
}
}
1.无重复字符的最长子串
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
还是一样,使用滑动窗口,假如未出现重复元素,则j++
扩大窗口,假如出现了则i++
缩小窗口。未重复的时候记录当前长度,最后输出最长长度。
注意,没有说过只有小写字母,所以用个set。
2.找到字符串中所有字母异位词
给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。
字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100。
说明:
字母异位词指字母相同,但排列不同的字符串。 不考虑答案输出的顺序。
本来我也很不解,想了半天滑动窗口,有思路但是没有具体措施。然后看了看评论,突然发现和前两个题的不同之处,那就是要找的子串长度固定,这也就是说用的是一个固定大小的滑动窗口,然后每次判断是否为字母异位词就好了。
判断的方式:对目的字符串用一个int数组统计每个字母出现次数,然后对于滑动窗口内的字符串也进行统计,最后比较两个数组即可。
lass Solution {
public List<Integer> findAnagrams(String s, String p) {
List<Integer> list = new LinkedList<Integer>();
if(s.length()<p.length()) return list;
int i = 0;
int j = p.length()-1;
int[] nums = new int[26];
for(int k=0;k<p.length();k++){
nums[p.charAt(k)-'a']++;
}
while(j<s.length()){
int[] tmp = new int[26];
for(int k=i;k<=j;k++){
tmp[s.charAt(k)-'a']++;
}
if(Arrays.equals(nums,tmp)) list.add(i);
i++;
j++;
}
return list;
}
}
代码可行,不过涉及数组的比较,于是运行十分缓慢,想到的第一个优化点是,如果遍历到了根本不属于目标字符串的字母,那么直接滑动窗口跳到它的后面。这样一来,时间消耗减少了一半。
3.最小覆盖子串
给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串。
示例:
输入: S = “ADOBECODEBANC”, T = “ABC” 输出: “BANC”
说明: 如果 S 中不存这样的子串,则返回空字符串 “”。
如果 S 中存在这样的子串,我们保证它是唯一的答案。
首先,用我薄弱的大脑想一想,可以设置一个哈希表,然后存储T中所有字符的对应出现次数。然后依然是不固定大小的滑动窗口遍历,如果未满足要求,就j++
,满足要求就i++
,每次满足要求时存储一下i
和j
的值,根据j-i
的大小取舍。
那么怎么判断是否满足条件呢?规定每次添加一个元素x,如果它在map中,那么值-1;每次删除一个元素x,如果它在map中,那么值+1。如果此时map中的值全为0,说明以及符合条件。
class Solution {
public String minWindow(String s, String t) {
Map<Character,Integer> map = new HashMap<Character, Integer>();
List<String> list = new ArrayList<>();
int count = 0; //count用来大致判断hashmap是否全为0,当然会有偏差,不过可以适当减少遍历map的次数
for(int i=0;i<t.length();i++){
map.put(t.charAt(i),map.getOrDefault(t.charAt(i),0)+1);
count++;
}
int i = 0;
int j = -1;
int resj = s.length()-1;
int resi = 0;
boolean flag = true; //判断是否不存在这样的子串
while(j<s.length()){
if(count<=0 && isEmpty(map)){ //此时满足要求
if(j-i<resj-resi){ //如果新的子串比旧的短,就存储
resi = i;
resj = j;
}
if(map.containsKey(s.charAt(i))){ //如果移除掉的是指定元素,则向map中添加
flag = false;
map.replace(s.charAt(i),map.get(s.charAt(i))+1);
count++;
}
i++; //滑动窗口缩小
}else{ //不符合的情况下
j++;
if(j<s.length()){
if(map.containsKey(s.charAt(j))){
map.replace(s.charAt(j),map.get(s.charAt(j))-1);
count--;
}
}
}
}
if(flag) return "";
return s.substring(resi,resj+1);
}
public boolean isEmpty(Map<Character,Integer> map){
for(Integer i:map.values()){
if(i>0) return false;
}
return true;
}
}