03 滑动窗口

文章介绍了滑动窗口算法,一种用于解决数组问题的高效方法,特别是在寻找连续子数组满足特定条件的情况下。通过使用双指针left和right,动态调整窗口大小,可以将时间复杂度从O(n^2)降低到O(n)。文中列举了4道LeetCode题目,展示了滑动窗口在不同场景下的应用,包括求解最小子数组和、解密循环数组、寻找最长连续两元素子序列和找到包含目标字符的最小子串。
摘要由CSDN通过智能技术生成

对应题目类型

  • 需要双层循环对数组进行遍历的题目可以考虑使用滑动数组法
  • 滑动窗口思想其实是对快慢双指针解决某类问题的改进

对应题目类型:

  1. 求解数组中连续的元素值满足某条件的连续最短子数组

解题思想

使用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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值