锋盈数科-知识库 Logo
首页
软件开发
计算机基础
Hello Halo
新手必读
关于本知识库
登录 →
锋盈数科-知识库 Logo
首页 软件开发 计算机基础 Hello Halo 新手必读 关于本知识库
登录
  1. 首页
  2. 软件开发
  3. 滑动窗口详解

滑动窗口详解

0
  • 软件开发
  • 发布于 2024-09-29
  • 0 次阅读
黄健
黄健



滑动窗口其实也就是之前介绍的同向双指针

步骤:

  1. 定义 left = 0, right = 0

  2. 进窗口

  3. 判断 出窗口 更新结果(更新结果的步骤根据具体题目中的要求进行)

  1. 长度最小的子数组

209. 长度最小的子数组


暴力解法:枚举出所有的情况,然后判断长度和区间和,这种方法的时间复杂度最优为O(n^2)

优化:利用单调性,使用同向指针优化

思路:定义left = 0,right = 0,right先往右移动,直到区间内的和符合条件,然后left往右移动并判断是否符合条件

当right移动到刚好符合条件的位置时,后面的就没必要进行枚举了,并且right也没有必要再从头再来枚举所有的情况了

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int left = 0, right = 0, sum = 0;
        int min = 100001;
        while (right < nums.length) {
            sum += nums[right];
            while (target <= sum) {
                min = Math.min(min, right - left + 1);
                sum -= nums[left];
                left++;
            }
            right++;
        }
        return (min == 100001) ? 0 : min;
    }
}
  1. LCR 016. 无重复字符的最长子串

LCR 016. 无重复字符的最长子串


暴力解法: 从第一个字符开始,固定每一个起始位置,一直往后枚举到出现重复字符,计算子串长度
再从下一个位置继续往后枚举,最终从枚举到的所有子串中找到最长的
判断重复元素时可以利用哈希表来判断(后面的题经常要用到这个技巧)

思路: 在暴力解法中可以发现,当遇到第一个重复字符时,只需要把left往后移动,跳过这个字符就可以继续往下枚举了,并且期间跳过的子串肯定是没有第一次枚举的子串长的,

还发现,当left跳过重复字符之后,right可以在原来的位置继续往后移动,不用再回过头来重新枚举字符串,这些子串的长度肯定也还是没有原来的长的,这样就又可以使用同向指针构成滑动窗口来解决这道题了

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int[] hash = new int[128];
        char[] chars = s.toCharArray();
        int max = 0;
        int left = 0, right = 0;
        while (right < s.length()) {
            //进窗口
            hash[chars[right]]++;
            //判断出窗口
            while (hash[chars[right]] > 1) {
                hash[chars[left]]--;
                left++;
            }
            //更新结果
            max = Math.max(max, right - left + 1);
            right++;
        }
        return max;
    }
}
  1. 最大连续1的个数 III

1004. 最大连续1的个数 III


翻转0的操作可以转化为查找一个区间最多有k个0,如果这个区间的0的个数在k个以内,那肯定就都可以翻转

暴力解法:还是通过两层for循环,依次枚举所有的可能,比较每种可能的长度

优化:通过双指针left,right,依次先确定left的位置,right向右移动,区间内有k个0之后,再把left右移,确定下一个区间,这时可以发现,如果left的位置不是0的话,再枚举k个0的区间肯定没有第一次枚举的区间长

第一个优化:left右移确定下一个起始位置时,right不用再回来

第二个优化:可以直接把left移动到下一个0的位置,此时right就可以往后移动

class Solution {
    public int longestOnes(int[] nums, int k) {
        int flag = 0, ans = 0;//flag为0的个数
        for (int left = 0, right = 0; right < nums.length; right++) {
            //进窗口
            if (nums[right] == 0) {
                flag++;
            }
            //判断出窗口
            while (flag > k) {
                if (nums[left++] == 0) {
                    flag--;
                }
            }
            ans = Math.max(right - left + 1, ans);
        }
        return ans;
    }
}
  1. 将 x 减到 0 的最小操作数

1658. 将 x 减到 0 的最小操作数


如果按照题目中的思路直接去考虑两端的数据就不太好想,所以换一种方式,考虑剩余的数据,左右两端的数据加起来等于x,那么中间的数据就等于nums数组和 - x ,也就是找出一段子串和等于 sum - x,再去比较出长度最长的子串,所以就转化为了使用滑动窗口求满足一定条件长度最长的子串的问题了

class Solution {
    public int minOperations(int[] nums, int x) {
        int sum = 0;
        int ans = -1;
        for (int a : nums)
            sum += a;
        if (sum < x)
            return -1;
        sum -= x;
        for (int left = 0, right = 0, tmp = 0; right < nums.length; right++) {
            tmp += nums[right];
            while (tmp > sum) {
                tmp -= nums[left++];
            }
            if (tmp == sum)
                ans = Math.max(right - left + 1, ans);
        }
        return (ans == -1) ? -1 : nums.length - ans;
    }
}
  1. 水果成篮

904. 水果成篮


这道题就是求满足一定条件的最长的子数组的问题

思路:使用哈希表来存储元素出现的次数,以此判断存储元素的种类,然后利用滑动窗口更新答案值

优化:如果使用HashMap的话,虽然说结果好存储,但是运行效率不高,所以采用数组模拟哈希表的方法,定义一个kind变量,来统计当前篮子中的水果数量

class Solution {
    public int totalFruit(int[] fruits) {
        int ans = 0;
        int[] hash = new int[fruits.length + 5];
        for (int left = 0, right = 0, kind = 0; right < fruits.length; right++) {
            if (hash[fruits[right]] == 0) {
                kind++;
            }
            hash[fruits[right]]++;
            while (kind > 2) {
                hash[fruits[left]]--;
                if (hash[fruits[left]] == 0) {
                    kind--;
                }
                left++;
            }
            ans = Math.max(ans, right - left + 1);
        }
        return ans;
    }
}
  1. 找到字符串中所有字母异位词

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


暴力解法:创建两个哈希表,然后把字符串p里面的每个字符存里面,接着遍历枚举所有s里面的和字符串p长度相等的子串,再把子串也存到哈希表中,对比两个哈希表中的值是否相等

滑动窗口优化:在枚举所有组合的过程中发现,因为需要维护子串的长度,所以如果right向右移动,left也要向右

移动,并且,存放到哈希表中的数据只是受到了left和right两个位置的影响,所以就可以使用滑动窗口进行枚举

判断条件优化:由于这道题只有26个字符,所以每次都遍历哈希表进行判断也可以,但是还可以进行优化

可以定义一个计数器cnt来统计枚举出来的组合的有效元素,在遍历的过程中,如果hashs中的数据大于hashp里的就代表出现了重复的元素,此时cnt就不用增加,在出窗口的过程中,如果hashs中的数据小于hashp里的,就表示此时滑出窗口的元素是一个有效值,cnt就需要减一,最后判断有效数据的个数是否等于字符串p的长度来更新结果

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        //存储结果
        List<Integer> ans = new ArrayList<>();
        int[] hashP = new int[26];
        char[] pp = p.toCharArray();
        //把字符串p存放到哈希表中
        for (char c : pp) {
            hashP[c - 'a']++;
        }
        int[] hashS = new int[26];
        char[] ss = s.toCharArray();
        for (int left = 0, right = 0, cnt = 0; right < ss.length; right++) {
            //进窗口 + 判断有效字符个数cnt
            if (++hashS[ss[right] - 'a'] <= hashP[ss[right] - 'a']) {
                cnt++;
            }
            //判断+出窗口
            if (right - left + 1 > pp.length) {
                if (hashS[ss[left] - 'a']-- <= hashP[ss[left] - 'a']) {
                    cnt--;
                }
                left++;
            }
            //有效字符相等,更新结果
            if (cnt == p.length()) {
                ans.add(left);
            }
        }
        return ans;
    }
}
  1. 串联所有单词的子串

30. 串联所有单词的子串


这道题其实和找出所有字母异位词特别像,只不过这道题把字母换成了字符串而已,那么就不能再使用普通的数组模拟哈希表来存储了,需要使用到容器来存储每一个字符串和出现的次数,然后就是一些细节问题的处理

  1. 由于是字符串,所以需要进行多次的滑动窗口,具体的次数根据给出的字符串数组中的字符串长度来看

  2. 注意,每一次使用滑动窗口都要重新创建一个哈希表

  3. 在更新cnt的时候,要注意此时最开始的字符串可能不在hash1中,直接调get方法会空指针异常

class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> ans = new ArrayList<>();
        //把words放进哈希表中
        Map<String, Integer> hash1 = new HashMap<>();
        for (String word : words) {
            hash1.put(word, hash1.getOrDefault(word, 0) + 1);
        }
        int len = words[0].length();
        //外层表示执行滑动窗口的次数
        for (int i = 0; i < len; i++) {
            //每次都要创建新的哈希表
            Map<String, Integer> hash2 = new HashMap<>();
            for (int left = i, right = i, cnt = 0; right + len <= s.length(); right += len) {
                //截取字符串,添加到哈希表中
                String str = s.substring(right, right + len);
                hash2.put(str, hash2.getOrDefault(str, 0) + 1);
                //hash1中也使用getOrDefault判断,可以避免空指针
                if (hash2.get(str) <= hash1.getOrDefault(str, 0)) {
                    cnt++;
                }
                if (right - left + 1 > words.length * len) {
                    String strl = s.substring(left, left + len);
                    if (hash2.get(strl) <= hash1.getOrDefault(strl, 0)) {
                        cnt--;
                    }
                    hash2.put(strl, hash2.get(strl) - 1);
                    left += len;
                }
                if (cnt == words.length) {
                    ans.add(left);
                }
            }
        }
        return ans;
    }
}
  1. 最小覆盖子串

76. 最小覆盖子串


暴力解法和上面的几题都很相似,直接来看使用滑动窗口优化之后的版本

思路:使用同向双指针维护一个窗口,使窗口内的子串涵盖字符串 t 所有字符,然后让left指针右移,此时会出现两种情况,一种是第一个和字符串t中字符相等的有很多,就算一个字符出窗口之后剩余的字符还会符合条件,另一种就是不符合条件的情况,需要right指针右移

判断出窗口的情况是窗口内的字符刚好符合题目要求,才开始出窗口,因为需要找到最小子串,那么更新结果也是在判断刚好符合条件之后就要更新结果了

优化:使用变量cnt来标记有效字符的种类,无论是进窗口还是出窗口,更新cnt时都需要保证两个哈希表中该该字符出现的种类数是相等的,否则cnt就会重复计数,最终判断有效字符个数cnt是否和hash1的元素个数相等就可以更新结果了

class Solution {
    public String minWindow(String s, String t) {
        int min = 100000, begin = -1;
        char[] chars = s.toCharArray();
        char[] chart = t.toCharArray();
        int[] hash1 = new int[128];
        int count = 0;
        for (char value : chart) {
            if (hash1[value] == 0) {
                count++;
            }
            hash1[value]++;
        }
        int[] hash2 = new int[128];
        //cnt : 有效字符的种类
        for (int left = 0, right = 0, cnt = 0; right < chars.length; right++) {
            //进窗口
            hash2[chars[right]]++;
            if (hash1[chars[right]] == hash2[chars[right]]) {
                cnt++;
            }
            //判断
            while (cnt == count) {
                //更新结果
                if (right - left + 1 < min) {
                    begin = left;
                    min = right - left + 1;
                }
                //出窗口
                if (hash2[chars[left]] == hash1[chars[left]]) {
                    cnt--;
                }
                hash2[chars[left]]--;
                left++;
            }
        }
        if (begin == -1)
            return new String();
        else
            return s.substring(begin, begin + min);
    }
}


原文链接: https://blog.csdn.net/2202_76097976/article/details/141811969

标签: #算法 139 #知识库 257
相关文章

万字:支付“核心系统”详解 2024-11-02 15:33

专栏作者:隐墨星辰 \| 主编:陈天宇宙 这篇文章也尝试化繁为简,探寻支付系统的本质,讲清楚在线支付系统最核心的一些概念和设计理念。 虽然支付行业已经过了风头最劲的时光,但跨境支付仍然在蓬勃发展,每年依然有很多新人进入这个行业,这篇文章尝试为这些刚入行的新人提供一点帮助。 文章只介绍一些支付行业十几

资深支付架构师视角:实战从问题定义到代码落地的完整套路 2024-11-02 15:33

前言 今天从一个实际案例入手,介绍站在架构师的角度,如何识别并定义问题,提炼需求,技术方案选型,再到详细设计,最后利用AI的能力协助写出核心的代码,验证与调优。 解决问题存在一定的模式,也可以称之为框架,总结出自己的思考和解题框架,以后再碰到同类型的问题就可以如庖丁解牛一样容易。 很多年前,我写代码

Spring 实现 3 种异步接口 2024-10-18 09:07

大家好,我是苏三~ 如何处理比较耗时的接口? 这题我熟,直接上异步接口,使用 Callable、WebAsyncTask 和 DeferredResult、CompletableFuture等均可实现。 但这些方法有局限性,处理结果仅返回单个值。在某些场景下,如果需要接口异步处理的同时,还持续不断地

重学SpringBoot3-集成Redis(五)之布隆过滤器 2024-10-08 11:24

更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-集成Redis(五)之布隆过滤器 1. 什么是布隆过滤器? * 基本概念 适用场景 2. 使用 Redis 实现布隆过滤器 * 项目依赖 Redis 配置

设计模式第16讲——迭代器模式(Iterator) 2024-10-08 11:24

一、什么是迭代器模式 迭代器模式是一种行为型设计模式,它提供了一种统一的方式来访问集合对象中的元素,而不是暴露集合内部的表示方式。简单地说,就是将遍历集合的责任封装到一个单独的对象中,我们可以按照特定的方式访问集合中的元素。 二、角色组成 抽象迭代器(Iterator):定义了遍历聚合对象所需的方法

vue2路由和vue3路由区别及原理 2024-10-08 11:24

一、Vue2 与 Vue3 路由的区别 1. 创建路由实例方式的不同 Vue 2 中,通过 Vue.use() 注册路由插件,并通过 new VueRouter() 来创建路由实例。 import Vue from 'vue';import VueRouter from 'vue-router';i

目录

IT 外包服务商

  • 意见投递
  • zyf6619

软件开发应用

主菜单

  • 首页
  • 软件开发
  • 计算机基础
  • Hello Halo
  • 新手必读
  • 关于本知识库
Copyright © 2024 your company All Rights Reserved. Powered by Halo.