力扣 | 滑动窗口

所谓滑动窗口,其实就是双指针有规律的移动,解决要在问题的集合中找到一个最大/最小区间符合条件的答案。

22/12/19更新: 距离这篇文章发布两年了,最近又碰到了滑动窗口的题目,还以为用动态规划做呢,死活想不出来。做完之后又总结了一下:

  1. 滑动窗口这个过程一般只是覆盖所有数据,得在窗口放大/窗口缩小时记录答案。(具体是缩小还是放大得看题意)

1.最大连续1的个数

8/13 力扣1004.

在这里插入图片描述

  • 解题思路: 当窗口内零的个数小于K时,窗口不断扩大。
    在窗口扩大的过程中,只要窗口内的零个数小于K,就有可能出现最长的数组长度。
class Solution {
    public int longestOnes(int[] A, int K) {
        int count=0 ,left =0 ,len=0 ;
        for(int right =0 ;right<A.length ;right++){
            if(A[right]==0) count++ ;
            while(count>K) {
                if(A[left]==0) count-- ;
                left++;
            } 
            len=Math.max(right-left+1 , len) ;
        }
        return len ;

    }
}

2.长度最小的子数组

8/15 力扣209.

在这里插入图片描述

  • 解题思路:同样地,先无脑将窗口扩大,然后再缩小,记录在这个过程中,滑动窗口长度的的最小值
class Solution {
public int minSubArrayLen(int s, int[] nums) {
    int left=0,right=0 ,tmp=0,len=0;
  
    while(right<nums.length ) {
        tmp+=nums[right] ;
        while(tmp>=s) {
            len=(len==0)?right-left+1: Math.min(len,right-left+1);
            tmp-=nums[left++];
        }
        right++; 
    }
    return len ; 

	}
}

从上面两道题来看,整个代码的框架没有什么大区别, 不同的地方就是在哪里更新结果 。
第一道题问最大长度,在扩展的过程中就有可能会获得答案,不一定需要满足条件, 于是在扩大窗口中不断更新答案。
第二道题求长度最小,要求一定会在满足条件的最小条件下,也就是缩短窗口内更新答案。

3.字符串的排列

8/16.力扣567
在这里插入图片描述

和上两道题处理数值加减的不同,滑动窗口在处理字符串时情况可能要更复杂一些,主要是体现在窗口的扩大和缩小时的具体操作。

思路:

  • 理解到要在s2中找到和s1有相同字母不同排列的区间, 先用滑动窗口定位到那个区间,再判断窗口中是否有该子串。

  • 在具体写代码的时候可能纠结于一些细节上, 可以就某一个点在题解中找到对应的解决方法。

  • 扩大窗口的时候,母串出现了多个相同目标字符,如何增加有效标记量? (缩小窗口时也会有相同情况)
    (如:母串aab ,子串:ab 如果只算出现的有效字符的个数,a就会把b排除在窗口外)


class Solution {
    public boolean checkInclusion(String s1, String s2) {
        if(s1==null||s2==null||s1.length()>s2.length()) return false ;
       
        int [] map =new int[26] ;//题目保证出现的字符都是小写字母
        int [] window=new int[26] ;   
        
        for(char c:s1.toCharArray()) map[c-'a']++ ;
        
        char [] s=s2.toCharArray() ;
        int left=0,match=0 ;

        for(int right=0 ;right<s.length;right++) {
            int ch=s[right]-'a' ;
            if(map[ch]>0) {
                window[ch]++ ; 
                if(map[ch]>=window[ch]) match++;//解决上述问题。 
            }
            while(match==s1.length()) {
                if(right-left+1==s1.length()) return true ;
                ch=s[left++]-'a' ;
                if(map[ch]!=0) {
//window包含目标串字符大于等于目标串的字符,遇到不符合的直接移除。
                    if(window[ch] <=map[ch]) match-- ;
                    window[ch]-- ;
                } 
            }
        }
        return false ;
    }
}

4.最小覆盖子串

8/16力扣76.

在这里插入图片描述
到目前为止已经刷了3道题,发现滑动窗口框架确实比较明显,这道题的思路和上题也差不多,变化的就是对结果的处理和一些顺序。



class Solution {
    public String minWindow(String s, String t) {
        char[] str=s.toCharArray();
        int window []=new int [128] ; 
        int map    []=new int [128] ;
        String res="" ;
        for(char c : t.toCharArray()) map[c]++ ;
        int left =0,right=0 ,match=0,len=str.length;
        
        while( right <str.length){
            int ch= str[right++];
            window[ch]++ ;
            if(map[ch]>0){
                if(window[ch]<=map[ch]) match++ ; 
            }
            while(match==t.length())  {
                if(len>=right-left) {
                    len=right-left;
                    res=s.substring(left,left+len) ;
                }
                ch=str[left]; 
                window[ch]--; 
                if(map[ch]>0 && window[ch] < map[ch]) {
                   match-- ;
                } 
                left++ ;
            }
        }  
        return res ; 
    }
}

扩展:找到字符串中所有字母异位词 力扣438.

刷到这题,无压力了,权当熟悉框架来做。


class Solution {
    public List<Integer> findAnagrams(String s, String p) {
      
        List<Integer> ans= new ArrayList<>() ;
        int [] map= new int[26] ;
        int [] window = new int[26] ;
       
        for(char c:p.toCharArray()) map[c-'a']++ ;
       
        char[] str=s.toCharArray() ;
        int right =0 ,left=0,match=0 ;
        while(right<str.length) {
            int ch= str[right++]-'a';
            if(map[ch]>0) {
                window[ch]++ ;
                if(map[ch]>=window[ch]) match++ ;
            }
            while(match==p.length()) {
                if(right-left==p.length()) ans.add(left) ;
                ch=str[left++ ]-'a';
                if(map[ch]>0) {
                    window[ch]-- ;
                    if(map[ch]>window[ch]) match-- ;
                }
              
            } 
        }
        return ans ; 
    }
}

再来一题别出新裁的:

5.替换后的最长重复字符

8/16力扣424.

在这里插入图片描述
本题是第一题难一点的版本,难在哪里?
首先,第一题0和1只有两种情况,非常好判断, 而这道题一看,需要面对26种情况,乍一看很复杂,其实经过思考,它面对的也是2种情况。

解题思路:

  • 前面4题,代码实现都有一个相似的操作, 称为套路,本题也一样沿用,基本框架基本不用细想。
  • 思考:题目求的是最长的区间长度,结果究竟应该在什么时候更新呢?

--------------------------------------------苦思冥想分割线-------------------------------

那么也就是说,结果不一定在缩小窗口种更新。

  • 所以缩小窗口的目的,是为了保证结果的每次更新都正确。
    想到这里就好办了, 结果集的更新应该在大循环的最后
  • 什么时候缩小窗口? 肯定是不满足条件的时候–也就是,k次替换后还有不同的字母。

用一个freq代表窗口内出现频率最大的字母,k次替换肯定是将窗口内其余字母全部替换成该字母。


class Solution {
    public int characterReplacement(String s, int k) {
        int[] window=new int[26] ;
       
        int right =0 ,left=0 ,freq=0,len=0 ;
       
        char str[]=s.toCharArray() ;
       
        while(right<str.length){
            int ch  = str[right++]-'A' ; 
            window[ch] ++ ;
            freq=Math.max(window[ch],freq) ; 
                
            while(right-left-freq>k ) {
                ch = str[left++]-'A';
                window[ch]-- ;
            }
            
            len=Math.max( len, right-left) ;
        }
        return len ;
    }
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值