每日刷题总结——滑动窗口

滑动窗口

  • 滑动窗口常见做题思路:
  1. 定义需要维护的变量们(对于滑动窗口类题目,这些变量通常是最小长度,最大长度,或者哈希表)
  2. 定义窗口的首尾端(start, end),然后滑动窗口
  3. 更新需要维护的变量, 有的变量需要一个if语句来维护(比如最大最小长度)
  4. Step 4 - 情况1
    //如果题目的窗口长度固定:用一个if语句判断一下当前窗口长度是否超过限定长度
    //如果超过了,窗口左指针前移一个单位保证窗口长度固定, 在那之前, 先更新Step 1 定义的(部分或所有) 维护变量
  5. Step 4 - 情况2
    //如果题目的窗口长度可变:这个时候一般涉及到窗口是否合法的问题
    //如果当前窗口不合法时, 用一个while去不断移动窗口左指针, 从而剔除非法元素直到窗口再次合法
    //在左指针移动之前更新Step 1 定义的(部分或所有) 维护变量
  • 代码实现:
public int problemName(String s) {
    //Step 1:定义需要维护的变量们(对于滑动窗口类题目,这些变量通常是最小长度,最大长度,或者哈希表)
    int x = ……;
    int y = ……;

    //Step 2:定义窗口的首尾端(start, end),然后滑动窗口
    int start = 0;
    int len = s.length();
    for (int end = 0; end < len; end++) {
        //Step 3:更新需要维护的变量, 有的变量需要一个if语句来维护(比如最大最小长度)
        x = new_x;
        if (condition) {
            y = new_y;
        }

        //------------下面是两种情况,读者请根据题意二选1------------//

        //Step 4 - 情况1
        //如果题目的窗口长度固定:用一个if语句判断一下当前窗口长度是否超过限定长度
        //如果超过了,窗口左指针前移一个单位保证窗口长度固定, 在那之前, 先更新Step 1 定义的(部分或所有) 维护变量
        if (窗口长度大于限定值) {
            //更新(部分或所有) 维护变量
            //窗口左指针前移一个单位保证窗口长度固定
        }
        //Step 4 - 情况2
        //如果题目的窗口长度可变:这个时候一般涉及到窗口是否合法的问题
        //如果当前窗口不合法时, 用一个while去不断移动窗口左指针, 从而剔除非法元素直到窗口再次合法
        //在左指针移动之前更新Step 1 定义的(部分或所有) 维护变量
        while (不合法) {
            //更新(部分或所有) 维护变量
            //不断移动窗口左指针直到窗口再次合法
        }
    }
    //Step 5:返回答案
    return ...;
}

159. 至多包含两个不同字符的最长子串

给定一个字符串 s ,找出 至多 包含两个不同字符的最长子串 t ,并返回该子串的长度。

示例 1:

输入: “eceba”
输出: 3
解释: t 是 “ece”,长度为3。

示例 2:

输入: “ccaabbb”
输出: 5
解释: t 是 “aabbb”,长度为5。

思路:
实际上这道题也是双指针的思想。

  1. 一个指针指向开头,一个指向末尾
  2. 当子字符串中含有的字符种类<=2,就更新end增加字符串长度
  3. 当子字符串中含有的字符种类 > 2,就更新start减少字符串长度
  4. 用一个max_len记录最长子字符串
  5. 用一个HashMap记录整个子字符串的字符种类不能超过2

代码实现:

class Solution {
    public int lengthOfLongestSubstringTwoDistinct(String s) {
        int len = s.length();
        if (len < 3) {
            return len;
        }

        int max_len = 0;
        Map<Character, Integer> map = new HashMap<>();//map用来记录子字符串的情况
        char[] ch = s.toCharArray();

        int start = 0;
        for (int end = 0; end < len; end++) {
            char tail = ch[end];
            if (map.containsKey(tail)) {
                map.put(tail, map.get(tail) + 1);//记录该字符的最大长度
            } else {
                map.put(tail, 1);//初次见面
            }
            if (map.size() <= 2) {//更新max_len
                max_len = Math.max(max_len, end - start + 1);
            }
            while (map.size() > 2) {//更新start
                char head = ch[start];
                if (map.containsKey(head)) {
                    map.put(head, map.get(head) - 1);
                }
                if (map.get(head) == 0) {
                    map.remove(head);
                }
                start++;//往后移动
            }
        }
        return max_len;
    }
}

340. 至多包含 K 个不同字符的最长子串

给定一个字符串 s ,找出 至多 包含 k 个不同字符的最长子串 T。

示例 1:

输入: s = “eceba”, k = 2
输出: 3
解释: 则 T 为 “ece”,所以长度为 3。

示例 2:

输入: s = “aa”, k = 1
输出: 2
解释: 则 T 为 “aa”,所以长度为 2。

思路:
跟上面那道题一样的,就是把2变成k

实现代码:

class Solution {
    public int lengthOfLongestSubstringKDistinct(String s, int k) {
        int len = s.length();
        if (len < k+1) {
            return len;
        }

        int max_len = 0;
        Map<Character, Integer> map = new HashMap<>();//map用来记录子字符串的情况
        char[] ch = s.toCharArray();

        int start = 0;
        for (int end = 0; end < len; end++) {
            char tail = ch[end];
            if (map.containsKey(tail)) {
                map.put(tail, map.get(tail) + 1);//记录该字符的最大长度
            } else {
                map.put(tail, 1);//初次见面
            }
            if (map.size() <= k) {//更新max_len
                max_len = Math.max(max_len, end - start + 1);
            }
            while (map.size() > k) {//更新start
                char head = ch[start];
                if (map.containsKey(head)) {
                    map.put(head, map.get(head) - 1);
                }
                if (map.get(head) == 0) {
                    map.remove(head);
                }
                start++;//往后移动
            }
        }
        return max_len;
    }
}

1004. 最大连续1的个数 III

给定一个二进制数组 nums 和一个整数 k,如果可以翻转最多 k 个 0 ,则返回 数组中连续 1 的最大个数 。

示例 1:

输入:nums = [1,1,1,0,0,0,1,1,1,1,0], K = 2
输出:6
解释:[1,1,1,0,0,1,1,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 6。

示例 2:

输入:nums = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3
输出:10
解释:[0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 10。

思路:
其实我个人觉得,滑动窗口其实就是一种双指针,一个指左,一个指右。某个条件下左指针移动,某个条件右指针移动。然后用一个int res,动态的记录最值。

  1. 左指针
  2. 右指针
  3. 左指针移动前是0,k++
  4. 右指针移动前是0,k–
  5. 当左指针或者右指针遍历到最后一个元素时结束

实现代码

public static int longestOnes(int[] nums, int k) {
    int len = nums.length;
    int res = 0;
    int i = 0, j = 0;
    while (i < len && j < len) {
        if (nums[j] != 0 || k != 0) {
            if (nums[j++] == 0) {
                k--;
            }
        } else {
            if (nums[i++] == 0) {
                k++;
            }
        }
        res = res > j - i ? res : j - i;
    }
    return res;
}

239. 滑动窗口最大值

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值 。

示例 1:

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置                最大值
     ---------------                    -----
[1 3 -1] -3 5 3 6 7                 3
1 [3 -1 -3] 5 3 6 7                 3
1 3 [-1 -3 5] 3 6 7                 5
1 3 -1 [-3 5 3] 6 7                 5
1 3 -1 -3 [5 3 6] 7                 6
1 3 -1 -3 5 [3 6 7]                 7

示例 2:

输入:nums = [1], k = 1
输出:[1]

思路:
维护一个长度为K的单调列,然后不停得往后遍历,拿出一个同时将不在视野内的都删除

代码实现:

public int[] maxSlidingWindow(int[] nums, int k) {
    int n = nums.length;
    PriorityQueue<int[]> pq = new PriorityQueue<int[]>((pair1, pair2) -> pair1[0] != pair2[0] ? pair2[0] - pair1[0] : pair2[1] - pair1[1]);
    for (int i = 0; i < k; ++i) {
        pq.offer(new int[]{nums[i], i});//前k个
    }
    //排好序了
    int[] ans = new int[n - k + 1];
    ans[0] = pq.peek()[0];
    for (int i = k; i < n; ++i) {
        pq.offer(new int[]{nums[i], i});//加入一个
        while (pq.peek()[1] <= i - k) {//将所有的不在视野内的东西都删除
            pq.poll();
        }
        ans[i - k + 1] = pq.peek()[0];
    }
    return ans;
}

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 的子串中,
因此没有符合条件的子字符串,返回空字符串。

思路:
很简单,也是双指针的思路。一个快指针在后面遍历,一个慢指针在前面用来缩减字符串。具体思路如下
在这里插入图片描述
但是你会发现如此方法能做出来,但是时间复杂度很高。
仔细阅读题目可以发现,字符串都是大小写的英文字母,因此可以采用对字符串的统计数组来简化计算。(实际上思想还是上面的思想)

代码实现

public static String minWindow(String s, String t) {
    char[] s1 = s.toCharArray();//将s字符串转化为字符数组
    char[] t1 = t.toCharArray();//将t字符串转化为字符数组
    int n = s1.length;//n记录s字符串的长度
    int m = t1.length;//m记录t字符串的长度
    int[] arr = new int[58];//z:122  A:65  122-65+1=58//对字符的个数统计使用的数组,如:0就是A即'A'-'A'

    int fast = 0, slow = 0, count = 0;//定义快指针fast,慢指针slow,记录符合的字符个数count
    String res = "";//定义返回的结果字符串
    //---------先遍历t字符串-----------//

    for (int i = 0; i < m; i++) {//首先将t中的字符,全部减一
        arr[t1[i] - 'A']--;
    }

    //---------再遍历s字符串---------//
    for (fast = 0; fast < n; fast++) {//滑动窗口快指针遍历
        arr[s1[fast] - 'A']++;//将快指针遍历过的字符全部加1,表示经过了

        if (arr[s1[fast] - 'A'] <= 0) {//如果是目标字符,则前面已经减一过了,所以只会<=0
            count++;//记录符合目标字符的个数
        }

        //发现符合情况的字符串,此时我需要将字符串缩减到最短以满足要求
        if (count == m) {//如果目标字符的个数等于t字符串的长度就开始慢指针遍历
            while (arr[s1[slow] - 'A'] > 0) {//如果慢指针指向的数>0则不是目标字符,就向前移动
                arr[s1[slow] - 'A']--;//因为已经脱离了快慢指针的范围就不统计个数了,于是就对1该字符个数减一
                slow++;//慢指针移动
            }

            if ("" == res || res.length() > fast - slow + 1) {//因为满足目标字符的条件,就开始截取字符串了
                res = s.substring(slow, fast + 1);//截取函数左闭右开,所以要fast+1
            }
        }
    }
    return res;//返回对应截取最短的字符串!
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

走出半生仍是少年

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

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

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

打赏作者

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

抵扣说明:

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

余额充值