滑动窗口问题复习

Posted 小智RE0

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了滑动窗口问题复习相关的知识,希望对你有一定的参考价值。

文章目录


当然可以在B站找一些教学视频学习

红桃A士算法

古城算法教学

简单来说;滑动窗口的思想就是

(1)当不满足当前的条件时,向右扩充,当满足条件时,向右收缩左边界,得到一个解后暂时保存,
(2)循环第一步,又得到一个解,将其与第一个解相对比,得到最优解并暂存,以此类推,直到窗口到达右边界停止。

1.基础题入手

1.1 力扣[3]: 无重复字符的最长子串

原题链接:无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
     
示例 4:
输入: s = ""
输出: 0
 
提示:
0 <= s.length <= 5 * 104
s 由英文字母、数字、符号和空格组成

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

使用滑动窗口解决

class Solution 
 public int lengthOfLongestSubstring(String s) 
     //滑动窗口思想;
     //在字符串中逐步向右移动;若有重复则左边界收缩;
     int n = s.length();
     if(n<2)
       return n;
     
     //这里可用字符数组来标记char字符;
     //使用int[] 数组来统计字符的频率数;
     char[] cArr = s.toCharArray();
     //s 由英文字母、数字、符号和空格组成;
     int[] nums = new int[128];
     
     //窗口的两个指针;
     int left  = 0;
     int right = 0;
     //结果至少是1;
     int res = 1;
     //只要窗口不超出字符串有边界范围;即可操作;
     while(right<n)
         //记录当前字符的频率数;
         nums[cArr[right]]++;
         //注意滑动时若重复则收缩左指针边界;
         while(nums[cArr[right]]==2)
             nums[cArr[left]]--;
             left++;
         
         //算长度;
         res = Math.max(res,right-left+1);
         right++;
     
     return res; 
 

也可用set集合的不重复性,配合指针解决

class Solution 
 public int lengthOfLongestSubstring(String s) 
     //字符串匹配过程;
     if(s==null || s.length()==0) 
        return 0;
     //指针解法;
     
     Set<Character> set = new HashSet<>();

     int zhen = -1; int res =0;
     for(int i =0;i<s.length();++i)
         if(i!=0)
             set.remove(s.charAt(i-1));
         
         //不重复就移动;
         while(zhen+1<s.length() && !set.contains(s.charAt(zhen+1)))
             set.add(s.charAt(zhen+1));
             ++zhen;
         
         //该段不重复字符的长度;
         res = Math.max(res,zhen-i+1);
     
     return res;
 


1.2 力扣[209]: 长度最小的子数组

原题位置:长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 
连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。
如果不存在符合条件的子数组,返回 0 。

示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

示例 2:
输入:target = 4, nums = [1,4,4]
输出:1

示例 3:
输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0
 
提示:
1 <= target <= 109
1 <= nums.length <= 105
1 <= nums[i] <= 105

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-size-subarray-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

滑动窗口

class Solution 
 public int minSubArrayLen(int target, int[] nums) 
     //滑动窗口思想;
     int left  = 0;
     int right = 0;
     int n = nums.length;
     int sum = 0;
     //设定一个默认的长度;
     int len = Integer.MAX_VALUE;
     while(right < n)
         //先移动右指针,去加数据;
         sum+=nums[right];
         //若>=目标值;由于要满足最短的子数组,则收缩左边界;
         while(sum>=target)
             len = Math.min(len,right - left +1);
             //收缩左边界;
             sum -= nums[left];
             left++;
         
         right ++;
     
     //由于len初始化为最大值了;这里需要排除;
     if(len == Integer.MAX_VALUE)
         return 0;
     
     return len;
 


2.其他题

基本还是用了滑动窗口的思想;需要考虑额外的要求;

2.1 力扣[424] :替换后的最长重复字符

原题链接:替换后的最长重复字符

给定一个字符串 s 和一个整数 k 。
您可以选择字符串中的任意字符,并将其更改为任何其他大写英文字符。该操作最多可执行 k 次。
在执行上述操作后,返回 包含相同字母的最长子字符串的长度 。

示例 1:
输入:s = "ABAB", k = 2
输出:4
解释:用两个'A'替换为两个'B',反之亦然。

示例 2:
输入:s = "AABABBA", k = 1
输出:4
解释:
将中间的一个'A'替换为'B',字符串变为 "AABBBBA"。
子串 "BBBB" 有最长重复字母, 答案为 4。
 
提示:
1 <= s.length <= 105
s 由小写英文字母组成
0 <= k <= s.length

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-repeating-character-replacement
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

使用滑动窗口,逐渐向右匹配,同时记录当前窗口内的重复字符个数;若在替换k次后,逼近于窗口的长度;那么当前窗口的长度即符合条件,返回即可;

class Solution 
    public int characterReplacement(String s, int k) 
        int len = s.length();
        if(len < 2) return len;
        //字符串转为数组;
        char[] Arr = s.toCharArray();
        //记录字符频率的数组;
        int[] temp = new int[128];
        //当窗口滑动时,此时的窗口内当前的字符个数;
        int tempCount =0;
        //最终结果;
        int res = 0;
        //滑动指针;
        int left  = 0;
        int right = 0;
        while(right < len)
            temp[Arr[right]]++;
            //记录个数;
            tempCount = Math.max(tempCount,temp[Arr[right]]);
            right ++;
            //如果当前的字符无法将窗口填充,需要将左窗口收缩;
            if(tempCount+k < right - left)
                temp[Arr[left]]--;
                left++;
            
            //记录此时的长度;
            res = Math.max(res,right - left);
        
        return res; 
    


2.2 力扣[76] : 最小覆盖子串

原题位置:最小覆盖子串

- 给你一个字符串 s 、一个字符串 t 。
- 返回 s 中涵盖 t 所有字符的最小子串。
- 如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。

注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。
 
示例 1:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"

示例 2:
输入:s = "a", t = "a"
输出:"a"

示例 3:
输入: s = "a", t = "aa"
输出: ""
解释: t 中两个字符 'a' 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。
 
提示:
1 <= s.length, t.length <= 105
s 和 t 由英文字母组成

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-window-substring
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

父串匹配子串问题,利用窗口找到匹配的字符起点位置;

如何保证匹配的字符相等?
-使用字符统计数组来统计当前字符的出现次数,若一致则说明匹配;

class Solution 
    public String minWindow(String s, String t) 
        //....当时 没注意,把faLen 和sonLen的长度都用了s的,拍错好久;
        //s:父串, t:子串;
        int faLen  = s.length();
        int sonLen = t.length();
        //特殊情况排除;
        if(faLen < sonLen)
            return "";
        
        //这里先将两个字符串变为字符数组;
        char[] FaArr  = s.toCharArray();
        char[] SonArr = t.toCharArray();
        //用于统计字符频率数的数组;
        int[] FaCount  = new int[128];
        int[] SonCount = new int[128];
        //子串固定的了,那就直接赋予;
        for(char c:SonArr)
            SonCount[c]++;
        
        //记录当前的划到的窗口内 包含的子串字符种类个数;
        int FaDistance = 0;
        //滑动窗口;
        int left = 0;
        int right = 0;
        //父串中满足条件的串长度;
        int goodLen = Integer.MAX_VALUE;
        //父串中符合条件的索引起点;
        int begin = 0;
        while(right < faLen)
            //当前的字符出现数量一样了;那么就增加父串的种类数;
            if(FaCount[FaArr[right]] < SonCount[FaArr[right]])
                 FaDistance ++;
            
            //记录当前字符出现的次数;
            FaCount[FaArr[right]]++;
            //父串先向右移动;
            right++;
            //注意;若果在父串中已经完全找到了子串;可考虑开始优化;
            while(FaDistance == sonLen)
                //这里算一下长度,同时记录符合的左窗口起点;
                if(goodLen > right-left)
                    goodLen = right-left;
                    begin = left;
                
                //若左指针对应的字符量此时已经匹配子串的量;则字符串的种类数减少;
                if(FaCount[FaArr[left]] == SonCount[FaArr[left]])
                    FaDistance --;
                
                FaCount[FaArr[left]]--;
                //左窗口缩进;
                left++;
            
        
        //这里要对满足的子串长度判断;
        if(goodLen ==Integer.MAX_VALUE )
            return "";
        
        //截取符合的字符串;
        return s.substring(begin,begin+goodLen);
    


2.3 力扣[438] :找到字符串中所有字母异位词

原题位置: 找到字符串中所有字母异位词

给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,
返回这些子串的起始索引。不考虑答案输出的顺序。
异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。

示例 1:
输入: s = "cbaebabacd", p = "abc"
输出: [0,6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。

 示例 2:
输入: s = "abab", p = "ab"
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词。
 
提示:
1 <= s.length, p.length <= 3 * 104
s 和 p 仅包含小写字母

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-all-anagrams-in-a-string
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这道题,在76题的基础上,稍微改动之处,
需要完全匹配子串的 长度和 字符;

class Solution 
    public List<Integer> findAnagrams(String s, String p) 
        List<Integer> list = new ArrayList<>();
        //滑动窗口问题;
        int FaLen  = s.length();
        int SonLen = p.length();
        if(FaLen<SonLen)
            return list;
         
        //先将子字符串转为数组;
        char[] SonArr = p.toCharArray();
        //统计字符串频率数的数组;
        int[] FaCount = new int[128];
        int[] SonCount= new int[128];
        //将子串的字符出现次数统计;
        for(char c : SonArr)
            SonCount[c]++;
        
        //父串中符合的字符种类数;
        int FaDistance =0;
        //子串当前的字符种类数;
        int SonDistance = 0;
        for(int i =0;i<SonCount.length;i++)
            if(SonCount[i]>0)
                SonDistance+=1;       
            
        
        int left =0;
        int right =0;
        while(right < FaLen)
            //窗口先向右滑动;
            char rightChar = s.charAt(right);
            //若当前子串有此字符再行动;
            if(SonCount[rightChar]>0)
                FaCount[rightChar]++;
                if(FaCount[rightChar] == SonCount[rightChar])
                    FaDistance++;
                
            
            right++;
            //当符合子串长度时;
            while(FaDistance == SonDistance)
                //标记合适的起点;
                if(SonLen == right-left)
                    list.add(left);
                
                char leftChar = s.charAt(left);
                //左窗口收缩;
                if(SonCount[leftChar]>0)
                    FaCount[leftChar]--;
                    if(FaCount[leftChar]<最长的最美好子字符串 java版本 滑动窗口解法并配有注释

算法:滑动窗口的两种精妙解法239. Sliding Window Maximum

剑指Offer滑动窗口的最大值(笔试&面试解法)

剑指Offer滑动窗口的最大值(笔试&面试解法)

算法复习:滑动窗口

动态规划_基础_任意子区间序列求和问题_滑动窗口解法_多种思路_分治思想演变