滑动窗口总结

滑动窗口思路

  1. 在字符串s中使用双指针的最有指针技巧,初始化left=right=0,把索引左闭右开区间 [left,right) 称为一个窗口。
  2. 不断地增加right指针扩大窗口[left,right),直到窗口中的字符符合要求(包含了T中所有字符)
  3. 停止增加right,转而不断增加left指针缩小窗口[left,right),直到窗口中的字符串不再符合要求(不包含T中的所有字符了)。
    同时每次增加left,我们都要更新一轮结果
  4. 重复2-3,直到right到达字符串s的尽头。

在第二步中寻找一个可行解、在第三步在优化这个可行解,最终找到最优解。左右指针轮流前进,窗口大小增增减减,窗口不断向右滑动。

模板

int left = 0, right = 0;
while(right<s.size(){
    window.add(s.charAt(right));
    right++;
    
    whild(valid){
        window.remove(s.charAt(left));
        left++;
    }
}

套模板四个问题

最小覆盖子串

//给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串。 
//
// 示例: 
//
// 输入: S = "ADOBECODEBANC", T = "ABC"
//输出: "BANC" 
//
// 说明: 
//
// 
// 如果 S 中不存这样的子串,则返回空字符串 ""。 
// 如果 S 中存在这样的子串,我们保证它是唯一的答案。 
// 
// Related Topics 哈希表 双指针 字符串 Sliding Window

package leetcode.editor.cn;

import java.util.HashMap;
import java.util.Map;

//Java:最小覆盖子串
public class P76MinimumWindowSubstring {
    public static void main(String[] args) {
        Solution solution = new P76MinimumWindowSubstring().new Solution();
        // TO TEST
        //  System.out.println("432101234".substring(0, 1));
        String S = "ADOBECODEBANC", T = "ABC";
        System.out.println(solution.minWindow(S, T));
    }


    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        public String minWindow0(String s, String t) {
            int left = 0, right = 0;
            int start = 0, minLen = Integer.MAX_VALUE;
            Map<Character, Integer> window = new HashMap<>();
            Map<Character, Integer> needs = new HashMap<>();
            for (char c : t.toCharArray()) {
                needs.put(c, needs.getOrDefault(c, 0) + 1);
            }
            //记录window中已经有多少字符符合要求了
            int macth = 0;
            while (right < s.length()) {
                //c1是移入窗口的字符
                char c1 = s.charAt(right);
                right++;
                if (needs.getOrDefault(c1, 0) != 0) {
                    window.put(c1, window.getOrDefault(c1, 0) + 1);
                    if (window.get(c1).equals(needs.get(c1))) {
                        //字符c1的出现次数符合要求了
                        macth++;
                    }
                }
                //window中的字符串已经符合needs的要求了
                while (macth == needs.size()) {
                    if (right - left < minLen) {
                        //更新最小字串的长度和位置
                        start = left;
                        minLen = right - left;
                    }
                    char c2 = s.charAt(left);
                    if (needs.get(c2) != null && needs.get(c2) != 0) {
                        //将c2字符移出window
                        window.put(c2, window.getOrDefault(c2, 0) - 1);
                        if (window.get(c2) < needs.get(c2)) {
                            //c2字符出现次数不再符合要求
                            macth--;
                        }
                    }

                    left++;
                }
            }
            return minLen == Integer.MAX_VALUE ? "" : s.substring(start, minLen);
        }

        public String minWindow(String s, String t) {
            //用来统计t中每个字符出现次数
            int[] needs = new int[128];
            //用来统计滑动窗口中每个字符出现次数
            int[] window = new int[128];

            for (int i = 0; i < t.length(); i++) {
                needs[t.charAt(i)]++;
            }

            int left = 0;
            int right = 0;

            String res = "";

            //目前有多少个字符
            int count = 0;

            //用来记录最短需要多少个字符。
            int minLength = s.length() + 1;

            while (right < s.length()) {
                char ch = s.charAt(right);
                window[ch]++;
                if (needs[ch] > 0 && needs[ch] >= window[ch]) {
                    count++;
                }

                //移动到不满足条件为止
                while (count == t.length()) {
                    ch = s.charAt(left);
                    if (needs[ch] > 0 && needs[ch] >= window[ch]) {
                        count--;
                    }

                    if (right - left + 1 < minLength) {
                        minLength = right - left + 1;
                        res = s.substring(left, right + 1);
                    }
                    window[ch]--;
                    left++;
                }
                right++;
            }

            return res;
        }
    }
//leetcode submit region end(Prohibit modification and deletion)

}

找到字符串中所有字母异位词

//给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。 
//
// 字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100。 
//
// 说明: 
//
// 
// 字母异位词指字母相同,但排列不同的字符串。 
// 不考虑答案输出的顺序。 
// 
//
// 示例 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" 的字母异位词。
// 
// Related Topics 哈希表

package leetcode.editor.cn;

import java.util.ArrayList;
import java.util.List;

//Java:找到字符串中所有字母异位词
public class P438FindAllAnagramsInAString {
    public static void main(String[] args) {
        Solution solution = new P438FindAllAnagramsInAString().new Solution();
        // TO TEST
    }


    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        public List<Integer> findAnagrams(String s, String p) {
            char[] arrS = s.toCharArray();
            char[] arrP = p.toCharArray();

            //接收最后返回的结果
            List<Integer> ans = new ArrayList<>();
            //定义一个needs数据来看arrP中包含元素的个数
            int[] needs = new int[26];
            //定义一个window数组来看滑动窗口中是否有arrP中的元素,并记录出现的个数
            int[] window = new int[26];

            //现将arrP中的元素保存到needs数组中
            for (char c : arrP) {
                needs[c - 'a'] += 1;
            }

            //定义滑动窗口的两端
            int left = 0;
            int right = 0;
            //右窗口不断向右一定
            while (right < arrS.length) {
                // curR 为当前元素的在数组中的索引
                int curR = arrS[right] - 'a';
                right++;
                //将窗口当前访问到的元素cur个数+1
                window[curR] += 1;
                //当window数组中cur比needs数组中对应元素的个数要多的时候就该移动左窗口指针
                while (window[curR] > needs[curR]) {
                    int curL = arrS[left] - 'a';
                    left++;
                    //将左窗口当前访问到的元素curL个数减1
                    window[curL] -= 1;
                }

                //这里将所有符合要求的左窗口索引放入到了接收结果的List中
                if (right - left == arrP.length) {
                    ans.add(left);
                }
            }

            return ans;
        }

    }
//leetcode submit region end(Prohibit modification and deletion)

}

无重复字符的最长子串

//给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。 
//
// 示例 1: 
//
// 输入: "abcabcbb"
//输出: 3 
//解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
// 
//
// 示例 2: 
//
// 输入: "bbbbb"
//输出: 1
//解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
// 
//
// 示例 3: 
//
// 输入: "pwwkew"
//输出: 3
//解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
//     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
// 
// Related Topics 哈希表 双指针 字符串 Sliding Window

package leetcode.editor.cn;
//Java:无重复字符的最长子串
public class P3LongestSubstringWithoutRepeatingCharacters{
    public static void main(String[] args) {
        Solution solution = new P3LongestSubstringWithoutRepeatingCharacters().new Solution();
        // TO TEST
    }
    

//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
    public int lengthOfLongestSubstring(String s) {
        int left = 0, right = 0;
        int[] window = new int[26];
        int res = 0;

        while (right < s.length()) {
            int c = s.charAt(right) - 'a';
            window[c]++;
            right++;

            //如果window中出现重复字符
            while (window[c] > 1) {
                int c2 = s.charAt(left) - 'a';
                window[c2]--;
                left++;
            }

            res = Math.max(res, right - left);
        }

        return res;
    }
}
//leetcode submit region end(Prohibit modification and deletion)

}

字符串的排列

//给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。 
//
// 换句话说,第一个字符串的排列之一是第二个字符串的子串。 
//
// 示例1: 
//
// 
//输入: s1 = "ab" s2 = "eidbaooo"
//输出: True
//解释: s2 包含 s1 的排列之一 ("ba").
// 
//
// 
//
// 示例2: 
//
// 
//输入: s1= "ab" s2 = "eidboaoo"
//输出: False
// 
//
// 
//
// 注意: 
//
// 
// 输入的字符串只包含小写字母 
// 两个字符串的长度都在 [1, 10,000] 之间 
// 
// Related Topics 双指针 Sliding Window

package leetcode.editor.cn;

import java.util.Collections;

//Java:字符串的排列
public class P567PermutationInString {
    public static void main(String[] args) {
        Solution solution = new P567PermutationInString().new Solution();
        // TO TEST
    }


    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        public boolean checkInclusion(String s1, String s2) {

            //排除异常的边界情况,也限定了模式串的长度
            if (s1.length() > s2.length()) {
                return false;
            }

            //匹配采用的窗口大小为模式串大小
            int windowSize = s1.length();

            //模式串的字典:可以看作是一种频率分布
            int[] map1 = new int[26];
            //动态更新的匹配窗口字典
            int[] map2 = new int[26];

            for (int i = 0; i < windowSize; i++) {
                map1[s1.charAt(i) - 'a']++;
                map2[s2.charAt(i) - 'a']++;
            }

            //对于每一轮滑窗查询,如果两个字典相等(评论分布一直,则命中)
            for (int i = windowSize; i < s2.length(); i++) {
                //两个字典相等,则命中
                if (Collections.singletonList(map1).equals(Collections.singletonList(map2))) {
                    return true;
                }

                //否则,向右滑窗:滑窗对于hash表的操作变为对应频率的增减
                map2[s2.charAt(i - windowSize) - 'a']--;
                map2[s2.charAt(i) - 'a']++;
            }

            //整个算法采用左闭右开区间,最后还有一个窗口没有判断
            return Collections.singletonList(map1).equals(Collections.singletonList(map2));
        }
    }
//leetcode submit region end(Prohibit modification and deletion)

}

待实现的问题

1、3. 无重复字符的最长子串
2、30. 串联所有单词的子串
3、76. 最小覆盖子串
4、159. 至多包含两个不同字符的最长子串
5、209. 长度最小的子数组
6、239. 滑动窗口最大值
7、340. 至多包含 K 个不同字符的最长子串
8、438. 找到字符串中所有字母异位词
9、567. 字符串的排列
10、632. 最小区间
11、727. 最小窗口子序列

参考文献:

https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/solution/hua-dong-chuang-kou-tong-yong-si-xiang-jie-jue-zi-/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352