目录
前言
本文主要总结一些和滑动窗口有关的算法题。当我们需要处理一段连续数据时往往可以考虑用滑动窗口。滑动窗口也可以称作同向双指针,主要分为四步。1、定义left=0,right=0,初始化指针 2、入窗口(移动右指针)3、判断条件出窗口(这个步骤往往要循环执行,出窗口指的是移动左指针,在判断条件的while循环里往往要有更改条件值的语句)4、更新值(这一步不一定是最后一步,也可能在中间完成,要根据具体情况分析)。
1、长度最小的子数组
链接:https://leetcode.cn/problems/minimum-size-subarray-sum/description/
往往滑动窗口的想法都来源于暴力解法,我们可以先思考一下这道题的暴力解法,那就是定义两个指针把所有区间都遍历一遍,在遍历的过程中我们尝试寻找可以优化的地方:当找到合适的区间时右指针再往外移动一定会比当前区间长,所以此次遍历没右指针没必要往后移动了,只需要移动左指针,而右指针也没必要回到左指针的位置重新遍历,只需要停留在原地,用刚刚计算好的sum值减去刚刚左指针指向的数得到新的sum值再加以判断。这样就演变成了左右指针算法。
java
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int left=0,right=0,sum=0;
int len=Integer.MAX_VALUE;
for(;right<nums.length;right++){
sum+=nums[right];
while(sum>=target){
len=Math.min(right-left+1,len);
sum-=nums[left];//在判断条件的while循环里往往要有更改条件值的语句
left++;
}
}
return len==Integer.MAX_VALUE?0:len;
}
}
2、无重复字符的最长字串
链接:https://leetcode.cn/problems/longest-substring-without-repeating-characters/description/
当提到字串子区间时都是连续区间,都可以考虑用滑动窗口。 本题和上题基本一致,也是从暴力遍历解法出发,只是判断条件略有不同。
java
class Solution {
public int lengthOfLongestSubstring(String s) {
int left=0,right=0,len=0;
char[] ss=s.toCharArray();
int[] hash=new int[128];
while(right<ss.length){
hash[ss[right]]++;
while(hash[ss[right]]>1){
hash[ss[left]]--;//在判断条件的while循环里往往要有更改条件值的语句
left++;
}
len=Math.max(len,right-left+1);
right++;
}
return len;
}
}
3、最大连续1的个数III
链接:https://leetcode.cn/problems/max-consecutive-ones-iii/submissions/589040538/
本题如果按照题目描述去翻转会非常复杂,遇到这类题可以尝试对目标进行转化。题目的要求可以等效为找到一个最长子序列,里面包含的0的个数要小于等于k,经过这样转化就可以用滑动窗口来解决了。
java
class Solution {
public int longestOnes(int[] nums, int k) {
int res=0;
int left=0,right=0;
int count=0;
while(right<nums.length){
if(nums[right]==0){
count+=1;
}
while(count>k){
if(nums[left]==0){
count--;
}
left++;
}
res=Math.max(res,right-left+1);
right++;
}
return res;
}
}
4、将x减到0的最⼩操作数
链接:https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/
本题如果按照题目描述去做会非常困难,因为又是需要减左边有时候需要减右边,正难则反易是一个很重要的算法思想。可以等价成从中间找一段连续数组,使他们的值相加为target=nums-x,想要两边的操作最少,则中间的序列长度要最长,于是题目转化为了找到一个目标值为target的最长子序列 ,用滑动窗口可解。
5、水果成篮
链接:https://leetcode.cn/problems/fruit-into-baskets/submissions/591282077/
本题重点是理清题目含义,多读几遍读懂题后发现就是滑动窗口的套路。
java
class Solution {
public int totalFruit(int[] fruits) {
Map<Integer,Integer> map=new HashMap<> ();
int left =0,right=0;
int res=0;
while(right<fruits.length){
int in=fruits[right];
map.put(in,map.getOrDefault(in,0)+1);
while(map.size()>2){
int out=fruits[left];
map.put(out,map.get(out)-1);
if(map.get(out)==0){
map.remove(out);
}
left++;
}
res=Math.max(res,right-left+1);
right++;
}
return res;
}
}
6、找到字符串中所有字⺟异位词
链接:https://leetcode.cn/problems/find-all-anagrams-in-a-string/description/
因为字符串 p 的异位词的⻓度⼀定与字符串 p 的⻓度相同,所以我们可以在字符串 s 中构造⼀个⻓度为与字符串 p 的⻓度相同的滑动窗⼝,并在滑动中维护窗⼝中每种字⺟的数量。当窗⼝中每种字⺟的数量与字符串 p 中每种字⺟的数量相同时,则说明当前窗⼝为字符串 p的异位词。因此可以⽤两个⼤⼩为 26 的数组来模拟哈希表,⼀个来保存 s 中的⼦串每个字符出现的个数,另⼀个来保存 p 中每⼀个字符出现的个数。这样就能判断两个串是否是异位词。
本题是滑动窗口的新颖用法,我们发现可以通过滑动窗口来维护一个固定长度的序列看是否满足条件。
java
class Solution {
public List<Integer> findAnagrams(String s, String p) {
List<Integer> res= new ArrayList<> ();
int[] hashs=new int[26];
int[] hashp=new int[26];
int length=p.length();
char[] parray = p.toCharArray();
for(char e:parray){
hashp[e-'a']++;
}
int left=0,right=0;
char[] sarray = s.toCharArray();
while(right<s.length()){
hashs[sarray[right]-'a']++;
if(right-left+1>length){
hashs[sarray[left]-'a']--;
left++;
}
int flag=1;
for(int i=0;i<26;i++){
if(hashp[i]!=hashs[i]){
flag=0;
break;
}
}
if(flag==1){
res.add(left);
}
right++;
}
return res;
}
}
7、串联所有单词的子串
链接:https://leetcode.cn/problems/substring-with-concatenation-of-all-words/description/
这道题如果没有上一道题的思路会非常非常难,但只要有了上一道题的思路就可以找到突破口。我们可以把words中的字符串等效成上一道题的字母。然后循环字符串的长度次,就可以解决问题。
java
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
int len = words[0].length();
List<Integer> res =new ArrayList<> ();
Map<String,Integer> hashwords =new HashMap<> ();
int m=words.length;
for(var str:words){
hashwords.put(str,hashwords.getOrDefault(str,0)+1);
}
for(int i=0;i<len;i++){
int count=0;
Map<String,Integer> hashs =new HashMap<> ();
for(int left=i,right=i;right+len<=s.length();right+=len){
String in=s.substring(right,right+len);
hashs.put(in,hashs.getOrDefault(in,0)+1);
if(hashs.get(in)<=hashwords.getOrDefault(in, 0)){
count++;
}
if(right-left+len>len*m){
String out=s.substring(left,left+len);
if(hashs.get(out) <= hashwords.getOrDefault(out, 0)) count--;
hashs.put(out,hashs.getOrDefault(out,0)-1);
left+=len;
}
if(count==m){
res.add(left);
}
}
}
return res;
}
}