算法-滑动窗口技巧

news2024/12/27 13:10:16

文章目录

    • 基础理论介绍
    • 长度最小的子数组
    • 无重复字符的最长字串
      • 解法1 : 哈希表计数逐步缩进
      • 解法2 : 哈希表更新下标跳跃缩进
    • 最小覆盖字串
    • 替换子串获得平衡字符串
    • K个不同整数的子数组

基础理论介绍

1. 滑动窗口简介 : 滑动窗口其实就是维持了一段区间(l边界与r边界), 并且对于这个窗口的两个边界来说, 左右指针都不会进行回退, 来求解一些关于字符组跟字串相关的问题(所以说我们的滑动窗口的大流程的时间复杂度一般都是O(n), 明显优于遍历每一个子区间的时间复杂度O(n^2)
2. 滑动窗口的核心 : 找到窗口的范围跟所求指标之间的单调性关系(这里的范围我们指的是对其中的单一窗口来说, 扩大或者缩小范围的影响)
3. 滑动窗口的过程 : 用简单变量或者简单结构来维持信息(通常是左右两个指针, 有时候也可以根据实际情况对指针的数量进行调整)
4. 求解大流程 : 求解子数组在每个位置开头或者结尾时的答案
5. 关于单调性分析经验 : 对于一个窗口来说, 我们考虑这个窗口向右侧左侧扩大是不是满足条件(上下界), 分析窗口是否回退的经验就是, 对当前窗口添加下一个元素, 看当前的窗口是不是也可以满足(不回退的分析)
6. 关于大流程经验 : 关于求解大流程, 如果是以结尾, 那么我们首先加入右侧的元素然后尝试缩进左侧元素(这里我们的边界是左闭右闭), 如果是以开头, 我们尝试向右侧扩充然后再删除左侧元素(这里我们的边界是左闭右开)

(补充)关于滑动窗口维持最大值/最小值的更新结构在单调队列章节讲述

长度最小的子数组

leetcode209题目链接
在这里插入图片描述
问题解析 :

  1. 首先看到是一个子数组字串的问题, 很自然的会联想到滑动窗口的相关技巧,下面就是找出来其中蕴含的范围与指标间的单调性关系
  2. 单调性分析 : 对于一个范围来说, 范围越大, 就表示这个区间内的数组总和越大, 就越逼近指标target, 所以有一个下限, 最短的数组长度
  3. 滑动窗口的过程 : 这个采用的就是经典的左右两个指针的方案
  4. 求解大流程 : 本题我们以结尾跟开头都进行尝试一下

以某个位置为结尾的答案

class Solution {
    //本次的解法采用的是以每一个位置为结尾的方案(左闭右闭的方案)
    public int minSubArrayLen(int target, int[] nums) {
        //返回的答案结果
        int resLen = Integer.MAX_VALUE;
        //求出来的和sum
        int sum = 0;
        for(int l = 0, r = 0; r < nums.length; r++){
            //首先添加右侧的元素
            sum += nums[r];
            //判断左边界是否可以向右侧缩进
            while(sum - nums[l] >= target){
                sum -= nums[l++];
            }
            //判断此时是否是答案
            if(sum >= target){
                resLen = Math.min(resLen, r - l + 1);
            }
        }
        return resLen == Integer.MAX_VALUE ? 0 : resLen;
    }
}

以某个位置为开头的答案

注意这种情况的分析, 一般需要注意边界的判断(因为我们这里采用的都是左闭右开的结构, 不同于结尾的大流程, 因为如果是左闭右闭的话,
很容易造成数据的重复)

class Solution {
    //本次的求解流程是以每一个位置开头
    public int minSubArrayLen(int target, int[] nums) {
        //返回的结果
        int resLen = Integer.MAX_VALUE;
        //子数组的求和sum
        int sum = 0;
        //下面就是求解的大流程(左闭右开)
        for(int l = 0, r = 0; l < nums.length; l++){
            //尝试向右侧扩展
            while(r < nums.length && sum < target){
                sum += nums[r++];
            }
            //判断此时是不是答案
            if(sum >= target){
                resLen = Math.min(resLen, r - l);
            }
            //删除左侧的元素
            sum -= nums[l];
        }
        return resLen == Integer.MAX_VALUE ? 0 : resLen;
    }   
}

无重复字符的最长字串

leetcode3题目链接
在这里插入图片描述
问题解析

  1. 首先看到这是一个求解子串的相关问题, 所以我们自然的想到了滑窗
  2. 单调性分析 : 对于一个范围来说, 范围越大就越容易产生重复的字符, 所以存在一个上限值, 也就是最长的子串长度
  3. 滑动窗口的过程 : 这个题没什么好说的, 还是维持两个指针进行滑动
  4. 求解大流程 : 从开头或者结尾开始均可

解法1 : 哈希表计数逐步缩进

也就是我们创建一个哈希表, 用来对每一个字符进行计数, 说是计数, 其实就是判断里面是不是有这一种字符, 所以map中的值其实只有1和2两种情况, 如果之前就没有出现过这一种字符, 那么直接加入map然后不用缩进继续进行循环, 如果之前有这种字符, 那么我们就尝试缩小区间的范围直到这种字符只剩下一个, 下一轮的循环不用回退(自己尝试画图理解单调性的本质)

以某个位置为结尾的答案

class Solution {
    //首先还是滑动窗口的大的流程(我们用一个map做一下词频统计)
    public int lengthOfLongestSubstring(String s) {
        //特殊字符串直接返回
        if(s == null || s.length() == 0) return 0;
        //返回的最大长度
        int resLen = 1;
        //把字符串变为字符数组方便操作
        char[] chs = s.toCharArray();
        //定义一个哈希表用来词频统计
        HashMap<Character, Integer> map = new HashMap<>();
        //下面是求解的大流程(以每一个字符为结尾)
        for(int l = 0, r = 0; r < chs.length; r++){
            //查看是否有右侧字符(如果没有直接加入继续, 如果有加入之后进行左边界的缩进)
            boolean isContains = map.containsKey(chs[r]);
            map.put(chs[r], map.getOrDefault(chs[r], 0) + 1);
            //进行左侧边界的缩进
            while(isContains && l < r){
                if(chs[l++] == chs[r]){
                    map.put(chs[r], 1);
                    break;
                }else{
                    map.remove(chs[l - 1]);
                }
            }
            //进行长度的更新
            resLen = Math.max(resLen, r - l + 1);
        }
        return resLen;
    }
}

以某个位置为开头的答案

class Solution {
    //首先还是滑动窗口的大的流程(我们用一个map做一下词频统计)
    public int lengthOfLongestSubstring(String s) {
        //特殊的字符串直接返回
        if(s == null || s.length() == 0) return 0;
        //返回的最大长度
        int resLen = 1;
        //把字符串变为字符数组方便操作
        char[] chs = s.toCharArray();
        //定义一个哈希表用来进行词频统计
        HashMap<Character, Integer> map = new HashMap<>();
        //下面就是求解的大流程(以每一个字符为开头)
        for(int l = 0, r = 0; l < chs.length; l++){
            //首先进行右侧边界的扩充
            while(r < chs.length && !map.containsKey(chs[r])){
                map.put(chs[r], map.getOrDefault(chs[r++], 0) + 1);
            }
            //进行边界的更新(左闭右开)
            resLen = Math.max(resLen, r - l);
            //删除左侧的值
            map.remove(chs[l]);
        }
        return resLen;
    }
}

解法2 : 哈希表更新下标跳跃缩进

上面的解法1虽说也是滑窗的思路, 但是不够好, 因为我们的两个边界都是逐步缩进的, 也就是说, 时间复杂度是两个O(n), 那有没有什么优化的方法呢, 我们把map改为元素以及对应的下标位置, 那么我们更新左边界的时候, 就可以通过获取元素上一个下标的位置的下一个下标以及原左边界进行比较大小, 大的那一个作为新的左边界, 这样就实现了左边界的跳跃更新, 虽然指标还是O(n), 但是可以在一定程度上加速原过程
以某个位置为结尾的答案

class Solution {
    //还是滑窗的思路, 但是我们的map存储的字符对应的下标位置
    public int lengthOfLongestSubstring(String s) {
        //特殊的字符串直接返回
        if(s == null || s.length() == 0) return 0;
        //返回的最大长度
        int resLen = 1;
        //把字符串变为字符数组方便操作
        char[] chs = s.toCharArray();
        //定义一个哈希表来进行下标映射
        HashMap<Character, Integer> map = new HashMap<>();
        //下面就是求解的大流程(以每一个字符为结尾)
        for(int l = 0, r = 0; r < chs.length; r++){
            //首先获取该元素的上一个位置下标
            int lastIndex = map.getOrDefault(chs[r], Integer.MIN_VALUE);
            //尝试更新左边界
            l = Math.max(l, lastIndex + 1);
            //尝试更新长度
            resLen = Math.max(resLen, r - l + 1);
            //加入新的下标位置
            map.put(chs[r], r);
        }
        return resLen;
    }
}

最小覆盖字串

leetcode76题目链接
在这里插入图片描述
问题解析 :

  1. 还是老思路, 子数组子串的问题, 我们可以联想到滑窗的相关技巧
  2. 单调性分析, 对于一个范围, 区间范围越大就越容易进行覆盖, 所以有一个下限也就是最下的子串长度
  3. 滑动窗口的过程 : 这个题没什么好说的, 还是维持两个指针进行滑动
  4. 求解大流程 : 以某一个位置为结尾

代码的实现直接看下面的代码(思路都写在题目上)

class Solution {
    public String minWindow(String s, String t) {
        //特殊情况直接返回
        if(s.length() < t.length()) return "";
        //转化为字符数组方便操作(也可以不转化)
        char[] cs = s.toCharArray();
        char[] ts = t.toCharArray();
        //创建一个词频统计的数组
        int[] cnt = new int[256];
        //对字符串的操作就是直接进行词频统计a
        for(char elem : ts){
            cnt[elem]--;
        }
        //创建一个欠债信息debt
        int debt = ts.length;
        //下面进行的滑动窗口的主流程(以某一个字符为结尾)
        int start = 0;
        int len = Integer.MAX_VALUE;
        for(int l = 0, r = 0; r < cs.length; r++){
            //添加当前右侧字符(中if就是一次有效的还款)
            if(cnt[cs[r]]++ < 0){
                debt--;
            }
            //只有debt为0才考虑进行答案收集
            if(debt == 0){
                //首先尝试往左侧回收区间
                while(cnt[cs[l]] > 0){
                    cnt[cs[l++]]--;
                }
                //尝试收集答案
                if(len > r - l + 1){
                    len = r - l + 1;
                    start = l;
                }
            }
        }
        return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len);
    }
}

替换子串获得平衡字符串

leetcode1234题目链接

class Solution {
    // 替换字串获得平衡的字符串(还是滑动窗口的思路)
    // 这个问题的单调性分析 : 范围越大, 则更容易得到平衡的字符串
    // 该问题我们从开头进行分析
    public int balancedString(String s) {
        // 把s转化为一个字符数组进行操作
        char[] cs = s.toCharArray();
        // 所需要的每种字符的个数
        int requ = cs.length / 4;
        // 把Q看作0, W看作1, E看作2, R看作3 进行词频统计
        int[] cnt = new int[4];
        for (char elem : cs) {
            cnt[getNum(elem)]++;
        }

        //返回的长度答案(最长就是字符串的答案)
        int resLen = cs.length;
        //下面是滑动窗口的主流程
        for(int l = 0, r = 0; l < cs.length; l++){
            //尝试向右侧进行扩充
            while(r < cs.length && !satisfy(cnt, l, r, requ)){
                int elem = getNum(cs[r]);
                cnt[elem]--;
                r++;
            }
            if(satisfy(cnt, l, r, requ)){
                resLen = Math.min(resLen, r - l);
            }
            cnt[getNum(cs[l])]++;
        }
        return resLen;
    }

    //根据词频判断该区间是不是满足要求的区间(cnt是不包含该区间的词频统计)
    private boolean satisfy(int[] cnt, int l, int r, int requ){
        //只要是其中有一种字符大于所需字符那就一定不行
        if(cnt[0] > requ || cnt[1] > requ || cnt[2] > requ || cnt[3] > requ) return false;
        if(4 * requ - (cnt[0] + cnt[1] + cnt[2] + cnt[3])  == r - l) return true;
        return false;
    }

    private int getNum(char elem){
        if(elem == 'Q') return 0;
        if(elem == 'W') return 1;
        if(elem == 'E') return 2;
        if(elem == 'R') return 3;
        return -1;
    }
}

K个不同整数的子数组

leetcode992题目链接

class Solution{
	public static int longestSubstring(String str, int k) {
		char[] s = str.toCharArray();
		int n = s.length;
		int[] cnts = new int[256];
		int ans = 0;
		// 每次要求子串必须含有require种字符,每种字符都必须>=k次,这样的最长子串是多长
		for (int require = 1; require <= 26; require++) {
			Arrays.fill(cnts, 0);
			// collect : 窗口中一共收集到的种类数
			// satisfy : 窗口中达标的种类数(次数>=k)
			for (int l = 0, r = 0, collect = 0, satisfy = 0; r < n; r++) {
				cnts[s[r]]++;
				if (cnts[s[r]] == 1) {
					collect++;
				}
				if (cnts[s[r]] == k) {
					satisfy++;
				}
				// l....r 种类超了!
				// l位置的字符,窗口中吐出来!
				while (collect > require) {
					if (cnts[s[l]] == 1) {
						collect--;
					}
					if (cnts[s[l]] == k) {
						satisfy--;
					}
					cnts[s[l++]]--;
				}
				// l.....r : 子串以r位置的字符结尾,且种类数不超的,最大长度!
				if (satisfy == require) {
					ans = Math.max(ans, r - l + 1);
				}
			}
		}
		return ans;
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2106061.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++:构造函数、析构函数

目录 一、类的默认成员函数 二、构造函数 构造函数的特点 三、析构函数 析构函数的特点 一、类的默认成员函数 默认成员函数就是用户没有显式实现&#xff0c;编译器会自动生成的成员函数称为默认成员函数&#xff0c;一个类&#xff0c;我们不写的情况下编译器会默认生成…

Unity(2022.3.41LTS) - 动画融合术

目录 一、动画融合的概念 二、动画融合的类型 三、动画融合的实现方法 1.简介 2.创建新的图层 3.创建遮罩 4.遮罩配置 5.预览效果 6.使用代码灵活控制 7.全部代码 四、动画融合的优化和注意事项 一、动画融合的概念 在 Unity 中&#xff0c;动画融合是一种强大的技…

安装 Let‘s Encrypt certbot 生成多个域名免费 https 证书实录(linux pip 方式)

本文记录了我在华为云 EulerOS linux 云主机使用 python pip 方式安装配置 Let’s Encrypt certbot, 并为我的网站的多个域名生成免费 https 证书的整个过程, 包括 python 环境配置, 下载 certbot 及 certbot-nginx, 一次性生成多个域名的证书及注意事项, 以及最后配置 certbot…

5G农业大数据中心顶层设计

1. 政策背景与规划 国家政策大力推动大数据发展和应用&#xff0c;特别是农业农村信息化发展规划&#xff0c;强调数字化在农业现代化中的关键作用。《数字农业农村发展规划&#xff08;2019-2025年&#xff09;》明确了农业农村数字化的重要性。 2. 国际农业大数据现状 美国…

超图iServer 11i在Java中调用REST接口点线面增删改查方法

一、引入iServer的Jar包 需要到iServer安装目录/webapps/iserver/WEB-INF/lib下寻找以下几个jar包&#xff0c;把它引入到项目里&#xff1a; iserver-all-*.jar service-model-*.jar rest-sdk-*.jar然后再引入几个maven依赖包&#xff1a; <dependency><groupId&g…

MyBaits 二级缓存原理

优质博文&#xff1a;IT-BLOG-CN 一级缓存原理 默认关闭&#xff0c;一般不建议使用。为什么不建议使用我们要清楚。 先给不建议使用的原因&#xff1a; MyBatis的二级缓存是和命名空间绑定的&#xff0c;所以通常情况下每一个Mapper映射文件都拥有自己的二级缓存&#xff0c;…

关于谷歌账号的三个“错误的”问题:谷歌有客服吗?登录不了的账号如何注销?登录不了的账号绑定的手机还能注册新账号吗?

这段时间GG账号服务收到很多朋友的反馈&#xff0c;其中有一些具有典型的问题&#xff0c;而且是错误的问题——主要是对谷歌账号或者谷歌账号使用的误解&#xff0c;从而浪费了时间&#xff0c;或者走了弯路&#xff0c;或者反复试错给账号带来了更大的风险。 今天就来给大家…

Spring 框架下 Redis 数据结构的全面解析

Hello , 大家好 , 这个专栏给大家带来的是 Redis 系列 ! 本篇文章给大家带来的是如何通过 Spring 来操作 Redis 中的常见数据结构 , 以及如何通过代码执行 Redis 中的原生命令 . 本专栏旨在为初学者提供一个全面的 Redis 学习路径&#xff0c;从基础概念到实际应用&#xff0c;…

【C++题解】1088 - 求两个数M和N的最大公约数

问题四&#xff1a;1088 - 求两个数M和N的最大公约数 类型&#xff1a;需要找规律的循环。 题目描述&#xff1a; 求两个正整整数 M 和 N 的最大公约数(M&#xff0c;N都在长整型范围内&#xff09; 输入&#xff1a; 输入一行&#xff0c;包括两个正整数。 输出&#xff…

Antv a-table 表格行/列合并,在合并后的td中使用插槽slot

【需求】 这次的表格需要实现行列合并&#xff0c;并且要在合并后的 td 中使用子组件或弹出弹窗&#xff0c;难点在于&#xff1a; 1. 根据提供的data&#xff0c;自行判断是否合并项的 getRowspan方法 2. customCell 、scopedSlots 冲突导致的子组件无法展示 &#xff08…

Cesium 实战 - 自定义纹理材质 - 流动线(精灵线)

Cesium 实战 - 自定义纹理材质 - 流动线(精灵线) 核心代码完整代码在线示例Cesium 给实体对象(Entity)提供了很多实用的样式,基本满足普通项目需求; 但是作为 WebGL 引擎,肯定不够丰富,尤其是动态效果样式。 对于实体对象(Entity),可以通过自定义材质,实现各种动…

【YOLOv8系列】YOLOv8的GUI界面设计;在电脑本地实现YOLOv8的可视化交互界面设计(对摄像头的实时画面进行分类)

背景: 最近在研究YOLOv8的应用,并且已经在自己的笔记本环境中跑通了YOLOv8的检测和分类算法,训练、验证、预测等功能均已实现;也通过自己的数据集训练出了自己的模型(权重);且之前也做了一个webUI界面,对YOLOv8检测和分类的结果进行展示;但是如果在本地的GUI界面调用摄…

Python pip 更换镜像源

文章目录 1 概述1.1 默认镜像&#xff0c;速度慢&#xff0c;易报错1.2 常用国内镜像源 2 更改镜像源2.1 临时更改2.2 永久更改2.2.1 查看配置源及配置文件2.2.2 编辑 pip.ini2.2.3 配置后的效果 1 概述 1.1 默认镜像&#xff0c;速度慢&#xff0c;易报错 默认镜像&#xff…

导出硬盘所有文件名到txt文本文件——C#学习笔记

下面的示例演示如何使用递归遍历目录树。递归方法很简洁&#xff0c;但如果目录树很大且嵌套很深&#xff0c;则有可能会引起堆栈溢出异常。 对于所处理的特定异常以及在每个文件和文件夹上执行的特定操作&#xff0c;都只是作为示例提供。您应该修改此代码来满足自己特定的需…

分类学习器(Classification Learner App)MATLAB

在MATLAB中&#xff0c;分类学习器用于构建和评估分类模型。MATLAB提供了一些工具和功能&#xff0c;帮助你进行分类任务&#xff0c;例如分类学习器应用程序、统计和机器学习工具箱中的函数等。 导入数据 我们在打开应用程序之前的第一步将是导入我们将在工作区使用的数据。…

新品上市丨科学级新款制冷相机sM4040A/sM4040B

sM4040B科学级显微制冷相机 特性 sM4040B搭载了 GSENSE4040BSI 3.2 英寸图像传感器&#xff0c;针对传感器固有的热噪声&#xff0c;专门设计了高效制冷模块&#xff0c;使得相机传感器的工作温度比环境温度低达 35-40 度。针对制冷相机常见的低温结雾现象设计了防结雾机制&a…

Serverless 应用引擎 SAE 助力袋拉拉研发提效 70%

作者&#xff1a;百潼 医院环保 IOT 设备的引领者&#xff1a;机汽猫 机汽猫是⼀家致⼒于通过投放⾃助取袋设备&#xff0c;为医院场景提供新型环保袋交付⽅式的科技公司。它成⽴于 2019 年&#xff0c;旗下品牌袋拉拉&#xff08;DaiLala&#xff09;通过投放⾃助取袋机&…

《Cloud Native Data Center Networking》(云原生数据中心网络设计)读书笔记 -- 10数据中心中的BGP

本章解答以下问题&#xff1a; ASN&#xff0c;团体&#xff08;community&#xff09;&#xff0c;属性&#xff08;attribute&#xff09;&#xff0c;最佳路径这些BGP术语是什么疑似&#xff1f;在数据中心中应该使用eBGP还是iBGP?在数据中心使用BGP时&#xff0c;应采用什…

序列化和反序列化之Serializable与Parcelable的异同

目录 序列化和反序列化Serializable 和 Parcelable 的区别Serializable特点Parcelable特点Serializable、Parcelable 使用场景区别总结 在 Android 开发中&#xff0c;序列化和反序列化是将对象转换为字节流以及从字节流还原对象的过程。Java 提供了 Serializable 接口&#xf…

jmeter中响应时间、TPS、服务器资源图表

一、响应时间图表 jmeter中的聚合报告已经足够显示响应时间&#xff0c;但是不会显示很详细&#xff0c;下面使用监听器中的插件查看&#xff0c; 添加后&#xff0c;可以不用更改任何配置&#xff0c;直接使用默认即可统计响应时间 还是抓取百度1分钟查看数据&#xff0c;也是…