四、滑动窗口-算法总结

四、滑动窗口

4.1 模板

/* 滑动窗口算法框架 */
void slidingWindow(string s, string t) {
    unordered_map<char, int> need, window;
    for (char c : t) need[c]++;

    int left = 0, right = 0;
    int valid = 0;
    while (right < s.size()) {
        // c 是将移入窗口的字符
        char c = s[right];
        // 右移窗口
        right++;
        // 进行窗口内数据的一系列更新
        ...

        // 判断左侧窗口是否要收缩
        while (window needs shrink) {
            // d 是将移出窗口的字符
            char d = s[left];
            // 左移窗口
            left++;
            // 进行窗口内数据的一系列更新
            ...
        }
    }
}

需要变化的地方

  • 1、右指针右移之后窗口数据更新
  • 2、判断窗口是否要收缩
  • 3、右指针右移之后窗口数据更新
  • 4、根据题意计算结果

4.2 示例

4.2.1 最小覆盖子串

76. 最小覆盖子串
在这里插入图片描述

// 使用Map的存储方式
class Solution {
    public String minWindow(String s, String t) {
        // 1、创建用于保存滑动窗口包含的字符集的map
        Map<Character,Integer> winChars =  new HashMap<>();
        // 2、创建保存子串中的字符集的map(注意重复的字符会累计次数)
        Map<Character,Integer> tChars = new HashMap<>();
        Integer temp;
        for(char c:t.toCharArray()){
            tChars.put(c,(temp=tChars.get(c)) == null?1:(temp+1));
        }

        int tCharNum = tChars.size(); // 子串中不同字符的个数
        int winCharNum = 0; // 滑动窗口中已匹配子串字符的个数(相同的字符要个数上匹配)
        int i = 0,j = 0; // 定义滑动窗口的左右边界
        int start = 0,end = 0; // 定义最终的滑动窗口的左右边界(即:最小尺寸的滑动窗口)
        int minWinLength = Integer.MAX_VALUE; // 初始化最小的滑动窗口值

        // 基于滑动窗口
        //先右移右指针直至包含所有的子串中的字符集, 再进行滑动窗口左边界右移
        while(j < s.length()){
            char c = s.charAt(j);
            if(tChars.get(c) != null){ // 若是子串中的字符, 记录当前的字符
                winChars.put(c,(temp=winChars.get(c)) == null?1:(temp+1));
                if(winChars.get(c).equals(tChars.get(c))){ // 判断是否已经有字符被完全匹配完(个数)
                    winCharNum++;
                }
            }
            j++; // 右移滑动窗口,注意此处操作会指向最小窗口右边界的下一个位置(由于字符子串获取是左闭右开)
            
            // 右移左边界(即:滑动窗口中已匹配完所有的子串字符)
            while(winCharNum == tCharNum){
                // 记录当前的滑动窗口长度
                if(j - i < minWinLength){
                    start = i;
                    end = j;
                    minWinLength = j - i;
                }

                char leftChar = s.charAt(i);
                if(tChars.get(leftChar) != null){ // 若右移左边界排除了一个子串中的字符
                    if(winChars.get(leftChar).equals(tChars.get(leftChar))){ // 该字符刚好个数完全匹配,则右移左边界会使总匹配个数减一
                        // 也可能滑动窗口中匹配的字符个数大于子串中该字符的个数
                        winCharNum--;
                    }
                    winChars.put(leftChar,winChars.get(leftChar)-1); // 更新滑动窗口中的字符集
                }
                i++;
            }
        }
        if(minWinLength == Integer.MAX_VALUE){ // 若没有更新,则返回空串
            return "";
        }
        return s.substring(start,end);
    }
}

// 使用数组的存储方式
class Solution {
public String minWindow(String s, String t) {
    // 技巧:用数组代替Map
    // 保存滑动窗口字符集
    int[] winMap = new int[256];
    // 保存需要出现的字符集
    int[] tMap = new int[256];
    for (char c : t.toCharArray()) {
        tMap[c]++;
    }
    // 计算共出现了多少不同的字符
    int charNum = 0;
    for (int n : tMap) {
        if (n > 0) {
            charNum++;
        }
    }
    // 滑动窗口左右边界
    int left = 0;
    int right = 0;
    // 匹配数
    int match = 0;
    // 窗口调整前暂存原窗口边界
    int start = 0;
    int end = 0;
    // 窗口长度的最小值
    int minValue = Integer.MAX_VALUE;
    while (right < s.length()) {
        char c = s.charAt(right);
        // 在需要的字符集里面,添加到窗口字符集里面
        if (tMap[c] != 0) {
            winMap[c]++;
            // 如果当前字符的数量匹配需要的字符的数量,则match值+1
            if (tMap[c] == winMap[c]) {
                match++;
            }
        }
        right++;
        // 当所有字符数量都匹配之后,开始缩紧窗口
        while (match == charNum) {
            // 获取结果
            if (right - left < minValue) {
                minValue = right - left;
                start = left;
                end = right;
            }
            char leftChar = s.charAt(left);
            // 左指针指向不在需要字符集则直接跳过
            if (tMap[leftChar] != 0) {
                // 左指针指向字符数量和需要的字符相等时,右移之后match值就不匹配则减一
                if (winMap[leftChar] == tMap[leftChar]) {
                    match--;
                }
                winMap[leftChar]--;
            }
            left++;
        }
    }
    if (minValue == Integer.MAX_VALUE) {
        return "";
    }
    return s.substring(start, end);
}
}

4.2.2 字符串的排列

567. 字符串的排列
在这里插入图片描述

// 使用数组的存储方式
class Solution {
    public boolean checkInclusion(String s1, String s2) {
        // 创建保存滑动窗口中字符集的数组
        int[] wChars = new int[256];
        // 创建保存子串字符集的数组(累计字符个数)
        int[] tChars = new int[256];

        // 记录子串的字符
        for(char c:s1.toCharArray()){
            tChars[c]++;
        }
        int tCharNum = 0; // 子串不同字符的个数 
        for(int i:tChars){
            if(i>0){
                tCharNum++;
            }
        }

        int left = 0,right = 0; // 滑动窗口的左右边界
        int winCharNum = 0; // 记录滑动窗口中匹配字符的数量(具有多个相同字符需要被匹配完)

        // 滑动窗口算法
        // 先右移有边界直至窗口内匹配完所有子串字符,接着做一左边界
        while(right < s2.length()){
            char c = s2.charAt(right);
            if(tChars[c] != 0){
                wChars[c]++; // 记录当前字符
                if(wChars[c] == tChars[c]){ // 对某一个字符完全匹配后记录数量
                    winCharNum++;
                }
            }
            right++; // 此时right指向窗口右边界的右边一个位置

            // 右移左边界
            while(winCharNum == tCharNum){
                c = s2.charAt(left);
                if(tChars[c] != 0){
                    if(wChars[c] == tChars[c]){ // 若当前字符数刚好等于子串中该字符的数量,则再减少一个就减少匹配数
                        winCharNum--;
                        if(right - left == s1.length()){ // 若窗口长度等于子串长度则为排列匹配
                            return true;
                        }
                    }
                    // 更新窗口中字符的信息
                    wChars[c]--;
                }
                left++;
            }
        }
        return false;


    }
}

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

438. 找到字符串中所有字母异位词
在这里插入图片描述

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        // 创建保存窗口中字符集的数组
        int[] wChars = new int[256];
        // 创建保存子串的字符集信息(累计字符个数)
        int[] tChars = new int[256];
        for(char c:p.toCharArray()){
            tChars[c]++;
        }

        // 记录子串中不同字符的数量
        int tCharsNum = 0;
        for(int i:tChars){
            if(i>0){
                tCharsNum++;
            }
        }

        int left = 0, right = 0; // 定义滑动窗口的左右边界
        int wCharsNum = 0; // 记录窗口中匹配的字符个数(相同字符需数量匹配完)

        // 记录结果
        List<Integer> result = new ArrayList<>();

        // 滑动窗口算法
        // 先右移有边界直至窗口匹配完所有子串的字符,然后右移左边界
        while(right < s.length()){
            char c = s.charAt(right);
            if(tChars[c] != 0){ // 当前字符为子串中出现的字符
                wChars[c]++; // 窗口中记录当前字符
                if(wChars[c] == tChars[c]){ // 字符数量匹配完则记录一个字符数量
                    wCharsNum++;
                }
            }
            right++;

            // 右移左边界
            while(wCharsNum == tCharsNum){
                c = s.charAt(left);
                if(tChars[c] != 0){ // 当前字符为子串中出现的字符
                    if(tChars[c] == wChars[c]){ // 若数量相等,则取出当前字符后则记录数少1
                        wCharsNum--;
                        if(right - left == p.length()){ // 当前left为满足匹配要求的临界边界;right则为右边界的后面一个位置。相等时则意味则排列匹配
                            // 记录左边界作为结果
                            result.add(left);
                        }
                    }
                    // 更新窗口字符信息
                    wChars[c]--;
                }
                left++;
            }
        }
        return result;

    }
}

4.2.4 无重复字符的最长子串

3. 无重复字符的最长子串
在这里插入图片描述

class Solution {
    public int lengthOfLongestSubstring(String s) {
        if(s.length() < 2){
            return s.length();
        }

        // 保存窗口中的字符集信息
        int[] wChars = new int[256];
        int maxSubLength = 0;
        int left = 0, right = 0; // 定义滑动窗口的左右边界

        while(right < s.length()){
            char c = s.charAt(right);
            right++;
            wChars[c]++; // 记录窗口中的字符信息

            // 右移左边界收缩窗口
            while(wChars[c] > 1){
                char leftChar = s.charAt(left);
                left++;
                wChars[leftChar]--;
            }
            maxSubLength = Math.max(maxSubLength,right - left);
        }
        return maxSubLength;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ModelBulider

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值