对应题目类型
- 需要双层循环对数组进行遍历的题目可以考虑使用滑动数组法
- 滑动窗口思想其实是对快慢双指针解决某类问题的改进
对应题目类型:
- 求解数组中连续的元素值满足某条件的连续最短子数组
解题思想
使用left以及right两个指针模拟一个窗口,然后通过控制两个指针的移动模拟窗口大小的变化与移动。
滑动窗口最关键的点在于循环是针对right指针的,right指针随着循环不断向后移动,
当窗口中的数据满足题目所需的条件时,
才会移动left指针,将窗口缩小。
- 暴力解法时间复杂度:O(n^2)
- 滑动窗口时间复杂度:O(n)
滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)的暴力解法降为O(n)。
滑动窗口需要注意三点:
1、右边界什么时候右扩;
2、左边界什么时候左缩==左边界左缩对应的条件;
3、右边界扩张时窗口相关的值要更新什么?左边界左缩时要更新什么值?
滑动窗口在第一层循环时,控制的是右边界:
# 解题模板:
left=0;
right=0;
while right<len(nums):
窗口右边界在向右扩张,因此要更新相关的值
while 窗口左缩对应条件:
先更新相关的值
然后将左边界左缩
right+=1
题目汇总
209.长度最小的子数组
https://leetcode.cn/problems/minimum-size-subarray-sum/
给定一个含有 n 个正整数的数组和一个正整数 target 。找出该数组中满足其和 ≥ target 的长度最小的连续子数组,返回其长度。
—>题意理解:求解数组中长度最小的满足和>=target的子数组的长度
- 窗口右扩更新的值:子数组的和
- 窗口左缩对应的条件:子数组的和>=target
- 窗口左缩更新的值:子数组的和,左边界
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
# 滑动窗口法--双指针操作
left=0
minlen=float('inf')
sum=0
for right in range(len(nums)):
sum+=nums[right]
while sum>=target:
minlen=min(minlen,right-left+1)
sum-=nums[left]
left+=1
right+=1
return 0 if minlen==float('inf') else minlen
1652. 拆炸弹
题目描述:
你有一个炸弹需要拆除,时间紧迫!你的情报员会给你一个长度为 n 的 循环 数组 code 以及一个密钥 k 。
为了获得正确的密码,你需要替换掉每一个数字。所有数字会 同时 被替换。
如果 k > 0 ,将第 i 个数字用 接下来 k 个数字之和替换。
如果 k < 0 ,将第 i 个数字用 之前 k 个数字之和替换。
如果 k == 0 ,将第 i 个数字用 0 替换。
由于 code 是循环的, code[n-1] 下一个元素是 code[0] ,且 code[0] 前一个元素是 code[n-1] 。
给你 循环 数组 code 和整数密钥 k ,请你返回解密后的结果来拆除炸弹!
解题思想:
分类处理:
当k=0时,直接将code全部变成0即可
当k>0时,code用其后面的k个数字之和替换
当k<0时,code用其前面的k个数字之和替换
可以先对数组中的每个元素求解其与其前面的k-1个元素的和,
然后k>0时,就是取i+k-1位置处的元素的sum
k<0时,就是取其前面的一个位置处的元素的sum
关键点:循环数组的处理
class Solution {
public:
vector<int> decrypt(vector<int>& code, int k) {
int n=code.size();
if(k==0){
fill(code.begin(),code.end(),0);
return code;
}
//先计算出每个位置与其前k-1个数的和
//使用滑动窗口计算
vector<int> sums(n,0);
int left=0,right=0,sum=0;
while(right<2*n){
sum+=code[right%n];
if(right-left==abs(k)-1){
sums[right%n]=sum;
sum-=code[left%n];
left++;
}
right++;
}
if(k<0) k=-1;
for(int i=n;i<2*n;i++){
code[i%n]=sums[(i+k)%n];
}
return code;
}
};
904.水果成篮
https://leetcode.cn/problems/fruit-into-baskets/
给定一个数组,要求得到该数组中连续的只有两个元素的长度最长的子序列,返回其长度。
- 窗口右扩更新的值:每种元素的数量以及元素的种类
- 窗口左缩对应的条件:元素的种类大于2
- 窗口左缩更新的值:左边界元素对应的数量以及所选的元素的种类
class Solution {
public int totalFruit(int[] fruits) {
//求解连续的只有两个元素的最长的子序列的长度
int len=fruits.length;
int num=0;
int maxnum=0;
Map<Integer,Integer> map=new HashMap<Integer,Integer>();
int l=0,r=0;
while(r<len){
map.put(fruits[r],map.getOrDefault(fruits[r],0)+1);
num++;
while(map.size()>2){
map.put(fruits[l],map.get(fruits[l])-1);
if(map.get(fruits[l])==0){//当map中对应的value变成0时记得要删除该key,否则size()是错误的
map.remove(fruits[l]);
}
l++;
num--;
}
r++;
maxnum=Math.max(maxnum,num);
}
return maxnum;
}
}
76.最小覆盖子串
https://leetcode.cn/problems/minimum-window-substring/
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
- 窗口右扩更新的值:子串中每个元素对应的元素数目
- 窗口左缩对应的条件:子串涵盖t中的所有字符
- 窗口左缩更新的值:子串中每个元素对应的元素数目
class Solution {
public String minWindow(String s, String t) {
int lens=s.length();
int lent=t.length();
if(lens<lent) return "";
Map<Character,Integer> map=new HashMap<>();
//先遍历t
for(int i=0;i<lent;i++){
map.put(t.charAt(i),map.getOrDefault(t.charAt(i),0)+1);
}
//然后对s执行滑动窗口
int l=0,r=0;
int minlen=Integer.MAX_VALUE;
String res="";
while(r<lens){
if(map.containsKey(s.charAt(r))){
map.put(s.charAt(r),map.get(s.charAt(r))-1);
}
while(func(map)){//func函数用于判断map中的value是否都<=0:如果是返回true
if(!map.containsKey(s.charAt(l))){
l++;
continue;
}
if(r-l+1<minlen){
minlen=r-l+1;
res=s.substring(l,r+1);
}
map.put(s.charAt(l),map.get(s.charAt(l))+1);
l++;
}
r++;
}
return res;
}
public boolean func(Map<Character,Integer> map){
for(int key:map.values()){
if(key>0) return false;
}
return true;
}
}