算法练习——滑动窗口

 前言:滑动窗口的难点不在于怎么编写代码,而在于如何想到这题是用滑动窗口的算法去解决。其次滑动窗口的左端和右端在滑动时窗口内数据存在单调性。

一:长度最小的子数组

题目要求:

解题思路:

对于第一道滑动窗口算法题而言,去熟悉这一过程,想想这个过程是怎么得到最终答案的,不用考虑为什么这题要使用滑动窗口。

题目要求是:找几个相邻的数据,他们的和大于等于target,要你求出相邻数据长度最小的值。

如果通过暴力算法,能解决,但是肯定会超时。暴力算法的缺陷时,重复的数据会多次计算,

问:那么如何优化算法,才能够避免重复计算呢?

答:我也答不出来,但是可以通过下图来尝试解释优化的过程。

我们的第一目标是要使得连续的几个数据相加的和 ≥ target,

那么我们定义一个 sum,

让 sum += arr[right++],直至满足条件时(示例1),如下图:

此时已经满足了题给条件。

问:接下来该怎么做呢?

答:定义一个 len 去记录实时的数据范围的长度。

问:定义好了然后呢?让right++吗?

答:显然right此时不应该++,因为再往右++,始终满足范围内数据大于 target ,且 len 不是我们所要的最小值。因此此时应该变动 left 了。

问:left 仅仅只是++吗?

答:显然也不是,如果仅仅是对left++ 的话,sum的数据仍然≥target,所以再left++之前,需要先更新sum的数据,即 sum-=arr[left],且应该是循环这个过程,直至sum不满足条件,之后right再次++,直至循环结束。

在这一过程,可以看到,当right为2时,我们让left++,这样就少考虑了right 右边 4 3 的情况,在后续遍历中,同样会避免对重复或者是无用数据的计算次数,因此在这一过程中,优化了算法。

实现代码:

int minSubArrayLen(int target, vector<int>& nums) {int n = nums.size();int len = INT_MAX;int sum = 0;for(int left = 0,right = 0;right < n;){sum+=nums[right];while(sum >= target){len = min(len,right - left + 1);//满足target条件时,更新len 再做变动sum -= nums[left];left++;}right++;}if(len == INT_MAX){return 0;}else{return len;}}

二:无重复字符的最长子串

题目要求:

解题思路:

上一题是借助 sum 是否满足target 来判断是否要出窗口

此题需要用到hash表(博主此时还没学过哈希,将哈希的思想理解成计数排序的思想)的思路去判断是否需要出窗口

借助下图来解释思路(示例1):

定义一个数组 int hash[128] = {0};先前我们学过计数排序的思想,即对应下标位置处的数据++;

hash[arr[right++]];right在向右遍历的过程中,对应的下标会不断++

注:arr[right]是字符数据,数组会将字符数据转换成对应的ASCII码值,即对应下标的数据会+1。

当 right 来到该位置处时,此时hash数组中,对应的下标位置处的数据已然为2,此时说明字串已经重复,此时需将a的ASCii码对应下标位置的数据--,再令left++

实现代码:

    int lengthOfLongestSubstring(string s) {int hash[128] = {0};int n = s.size();int len = 0;for(int left = 0,right = 0;right <n;){hash[s[right]]++;while(hash[s[right]] > 1){hash[s[left]]--;left++;}len = max(len,right - left+1);right++;}return len;}

三:将x减到0的最小操作数

题目要求:

解题思路:

这道题的关键在于逆向思维,读了这么多年书了,逆向思维大家应该都不陌生了。

        如果我们直接做这道题,会发现既要在左边遍历,又要在右边遍历,还要对比数据大小,那么这道题的难度就会变得很夸张。

        不妨多思考一下,换一个角度考虑。例如不去找两头的数据,而是中间的呢?不难发现,中间的数据是连在一起的,中间数据的值 = 数据总和 - x

        那么这道题就被转换成了:在数组中找一串连续的数据,满足以下两个要求

        ①:连续数据的和为:数据总和 - x

        ②:连续数据的长度最长

滑动窗口算法:

该算法有固定的部分:将数据入窗口,将数据出窗口

变化的部分是:出窗口的条件,以及何时更新我们想要的结果,和部分细节上的问题。

以下图为例:目标找满足和为20的数

定义 sum = 0;

开始时,持续入窗口 sum+= nums[right]; right++;

当指针来到下图位置时,sum > 20

此时需要出窗口,并更新窗口内数据大小,直至他不满足 > 20

当left指针来到下图位置时,恰巧找了我们需要的目标值

此时更新结果值,并继续循环直至结束。

代码实现:

 int minOperations(vector<int>& nums, int x) {int sum_num = 0;for(int i = 0; i < nums.size(); i++){sum_num += nums[i];}int target = sum_num - x;if(target < 0){return -1;}int sum = 0;int len = -1;for(int left = 0, right = 0; right < nums.size();right++){sum += nums[right];while(sum > target){sum -= nums[left];left++;}if(sum == target){len = max(len , right - left + 1);}}if(len == -1){return len;}else{return nums.size() - len;}}

注:上述代码有个注意点:target 可能为负,此时需要直接返回 -1

四:水果成篮

题目要求:

解题思路:

分析:简化一下题目,在一个整型数组fruit中,找到只包含两种数字的最大子串。

思路:借助哈希表——unordered_map<int,int> hash;来记录每种数字,以及其出现的次数

以示例4为例,初始情况如下图

进窗口

hash[f[right]]++; 记录当前水果,以及其出现次数,当right如下图位置时,需要出窗口

出窗口

当 hash.size() > 2 时,说明此时窗口内数字种类已超过2,不满足题目要求。。此时left需要++,但是left++前,需更新当前窗口信息,hash[f[left]]--;

当hash[f[left] == 0];时,此时需将该数字从hash表中删除

需要循环出窗口,直至满足数字个数要求,如下图所示:

更新结果

每次进窗口时,更新Max值

实现代码:

class Solution {
public:int totalFruit(vector<int>& f) {unordered_map<int,int> hash;int Max = 0;for(int left = 0,right = 0; right < f.size(); right++) {hash[f[right]]++;while(hash.size() > 2) {hash[f[left]]--;if(hash[f[left]] == 0) {hash.erase(f[left]);}left++;}Max = max(Max,right-left+1);}return Max;}
};

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

题目要求:

解题思路:

法一(以示例1为例):如下图定义两个变量

定义一个哈希表——int hash[26] = {0}; 来记录每个字母出现的个数

进窗口

hash[s[right] - 'a']++;直至如图所示

出窗口

当right - left + 1 > 子串长度(p.size())时,left需++,在left++前,需要先更新当前窗口,即hash[s[left] - 'a']--;

注:本题中,窗口大小是固定的,因此只需要出一次窗口即可

更新结果

当 right - left + 1 = 子串长度,写一个判断函数,将hash与p传入,判断是否为异位词,是则更新结果

法二(以示例1为例):区别于法一的是,通过维护另一个count变量,来更新结果。

初始变量如下图所示:

解释

hash1[26]用于记录子串p中,所有字母出现的个数

hash2[26]用于记录字符串s中,left~right所有字母出现的个数

进窗口:

hash2[s[right] - 'a']++;

判断 if(hash2[s[right] - 'a'] <= hash1[s[right] - 'a']) 若满足条件,说明此时hash2中进入了一个有效的字母,所以count++

解释:当前right处的字母为c,hash2[s[right] - 'a']++;将字母c插入到了hash2中,因为hash1中也包含了字母c,且只有一个,那么说明hash2中插入的字母为有效字母,count需要++,👉:假设插入的第二个字母也是c,但hash1中只有一个字母c,那么此时hash2中字母c的个数大于hash1中字母c的个数,说明第二次插入的c不是有效字母,因此count不++;

👉:假设插入的第二个字母是e,但是hash1中没有字母e,说明插入的也不是有效字母,因此count不++

循环进窗口直至如图所示

出窗口

当right - left + 1 > 子串长度时,需要出窗口,如图所示

hash2[s[left] - 'a']--;

判断if(hash2[s[left] - 'a'] < hash1[s[left] - 'a']) count--;

解释

需要判断当前出hash2的字母是否为有效字母,若为有效字母,则count--;如图所示

可以看到此时count == 2,无法满足更新结果的条件

更新结果

当 count == 子串长度时,说明此时hash2和hash1互为异位词。left为答案之一。如图所示

实现代码:

法二

class Solution {
public:vector<int> findAnagrams(string s, string p) {int hash1[26] = {0}, hash2[26] = {0};for(auto& w : p) {hash1[w - 'a']++;}vector<int> ret;for(int left = 0, right = 0, count = 0, len = p.size(); right < s.size(); right++) {hash2[s[right] - 'a']++;if(hash2[s[right] - 'a'] <= hash1[s[right] - 'a']) {count++;}if(right - left + 1 > len) {hash2[s[left] - 'a']--;if(hash2[s[left] - 'a'] < hash1[s[left] - 'a']) {count--;}left++;}if(right - left + 1 == len && count == len) {ret.push_back(left);      }}return ret;}
};

六:串联所有单词的子串

题目要求:

解题思路:

明确变量

int len = words[0].size(); → words中,每一个子串的长度

int total = words.size();   → words的个数:

以示例1为例

思考一个问题:left和right该怎么更新?每次+1吗?

:显然不可以,如果一个一个更新,那么每次只能统计一个字母,当满足right - left + 1 = len 时,在记录当前字符串,这显然太复杂了!!!!所以left和right每次应该+=len

定义一个变量 string in = s.substr(right,len);来构建当前s中的子串,进而做进一步的讨论。

思考另一个问题:这么做带来的问题就是,以第二个或者第三个为起始的子串没法遍历。

:为此我们需要在外部再套一层循环,循环len次。如图所示,一次讨论一种情况

思路:明确上述后,本题的思路就和第五题的法二没有太大区别了。

明确变量

unordered_map<string,int> hash1; 用于记录p中所有字符串出现的个数

unordered_map<string,int> hash2; 用于记录当前left~right+len所构成的字符串出现的次数

起始如图所示

进窗口

string in = s.substr(right,len);

hash2[in]++;

判断 if(hash1.count(in) && hash2[in] <= hash1[in]) count++;若当前进窗口的字符为有效字符,则count++;

出窗口

当right - left + 1 > total * len 时,需要出窗口,因为窗口大小固定,所以只需要出一次。

string out = s.substr(left,len);

hash2[out]--;

判断 if(hash1.count(out) && hash2[out] < hash1[out]) 若当前出窗口的字符为有效字符,则count--;

left += len;

可以看到此时窗口大小满足题目答案要求,但是count = 1,说明有效字符串个数不足,因此此时left处不满足题目要求。

更新结果

如果count == total,则left位置处为答案之一。

实现代码:

class Solution {
public:vector<int> findSubstring(string s, vector<string>& words) {unordered_map<string,int> hash1;for(auto str : words) {hash1[str]++;}vector<int> ret;int len = words[0].size(), total = words.size();for(int i = 0; i < len; i++) {unordered_map<string,int> hash2;for(int left = i, right = i,count = 0; right + len <= s.size(); right += len) {string in = s.substr(right,len);hash2[in]++;if(hash1.count(in) && hash2[in] <= hash1[in]) {count++;}if(right - left + 1 > total * len) {string out = s.substr(left,len);hash2[out]--;if(hash1.count(out) && hash2[out] < hash1[out]) {count--;}left += len;}if(count == total) {ret.push_back(left);}}}return ret;}
};

七:最小覆盖子串

题目要求:

解题思路:

思路

明确变量:

int Begin = -1; 用于记录最小子串的起始位置

int Min = INT_MAX; 用于记录最小子串的长度

int hash1[128] = {0}; 用于记录子串t中所有字母出现的个数

int hash2[128] = {0}; 用于记录字符串s中 left~right 所有字母出现的个数

int count = 0; 用于记录hash2中,有效字母的个数

初始如下图所示:

进窗口

 hash2[s[right]]++;

判断if(hash2[s[right]] <= hash1[s[right]]),若满足条件,说明此时进入hash2的为一个有效字母,则count++;

出窗口

当count == t.size()时,说明此时hash2中已经包含了hash1中所有的字母,如图所示:

1.在窗口中更新结果

判断if(right - left + 1 < Min),若满足条件,则Begin = left,Min = right - left + 1;

2.再出窗口

hash2[s[left]]--;

3.更新窗口信息

if(hash2[s[left]] < hash1[s[left]]) 若条件成立,则说明出窗口的字母为有效字母,则count--;

4.left++;

:此处需要循环出窗口

解释:第一次满足条件时,因为hash2中出去的第一个字母就是有效字母,为此循环直接结束,当第二次满足条件时,此时如图所示

hash2中出去的第一个字母不是有效字母,count仍然等于3,

若并非循环出窗口,那么在right到字符串末尾时,都无法得到我们想要的答案,

若为循环出窗口,可以看到,hash2中会将无效字母循环去除,在这个过程中Min的值会逐渐减小,直至有效字母的个数变为2,此时得到一个临时的Min和Begin。

当第三次满足条件时,如图所示

循环出窗口至如图所示:

因为是先更新的Min和Begin,left在++,所以此时Min和Begin就是我们想要的答案。

实现代码:

class Solution {
public:string minWindow(string s, string t) {int hash1[128] = {0};int hash2[128] = {0};for(auto& w : t) {hash1[w]++;}int Min = INT_MAX;int Begin = -1;for(int right = 0, left = 0, count = 0; right < s.size(); right++) {hash2[s[right]]++;if(hash2[s[right]] <= hash1[s[right]]) {count++;}while(count == t.size()) {if(right - left + 1 < Min) {Min = right - left + 1;Begin = left;}hash2[s[left]]--;if(hash2[s[left]] < hash1[s[left]]) {count--;}left++;}}if(Begin == -1) {return "";}else {return s.substr(Begin,Min); }}
};

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Zabbix-监控SSL证书有效期

背景 项目需要&#xff0c;需要监控所有的SSL证书的有效期&#xff0c;因此需要自定义一个监控项 实现 创建自定义脚本 在Zabbix的scripts目录(/etc/zabbix/scripts/)下创建一个新的shell脚本check_ssl.sh&#xff0c;内容如下 #!/bin/bash time$(echo | openssl s_client…

VSCode中出现“#include错误,请更新includePath“问题,解决方法

1、出现的问题 在编写C程序时&#xff0c;想引用头文件但是出现如下提示&#xff1a; &#xff08;1&#xff09;首先检查要引用的头文件是否存在&#xff0c;位于哪里。 &#xff08;2&#xff09;如果头文件存在&#xff0c;在编译时提醒VSCode终端中"#include错误&am…

讯方·智汇云校华为授权培训机构的介绍

官方授权 华为授权培训服务伙伴&#xff08;Huawei Authorized Learning Partner&#xff0c;简称HALP&#xff09;是获得华为授权&#xff0c;面向公众&#xff08;主要为华为企业业务的伙伴/客户&#xff09;提供与华为产品和技术相关的培训服务&#xff0c;培养华为产业链所…

LabVIEW商业软件开发

在商业软件开发和仪器自动测试领域&#xff0c;LabVIEW以其图形化编程方式、高效的数据采集能力和强大的硬件集成优势&#xff0c;成为众多工程项目的核心开发工具。然而&#xff0c;商业软件的开发远不止编写代码和实现功能那么简单&#xff0c;尤其是在仪器自动测试领域&…

优化关键词还有哪些软件可用?

随着2025年互联网的发展&#xff0c;越来越多的企业认识到关键词优化的重要性。SEO&#xff08;搜索引擎优化&#xff09;作为提升网站流量和排名的重要手段&#xff0c;已经成为每个企业营销战略中的核心组成部分。而在SEO优化过程中&#xff0c;关键词的选择和优化无疑是至关…

程序诗篇里的灵动笔触:指针绘就数据的梦幻蓝图<9>

大家好啊&#xff0c;我是小象٩(๑ω๑)۶ 我的博客&#xff1a;Xiao Xiangζั͡ޓއއ 很高兴见到大家&#xff0c;希望能够和大家一起交流学习&#xff0c;共同进步。 这一节是对之前内容的修整 目录 一、传值调用和传址调用二、数组名的理解三、指针访问数组四、结尾 一…

新一代SCADA: 宏集Panorama Suite 2025 正式发布,提供更灵活、符合人体工学且安全的应用体验

宏集科技宣布正式推出全新Panorama Suite 2025 SCADA软件&#xff01;全新版本标志着 Panorama Suite的一个重要里程碑&#xff0c;代表了从 Panorama Suite 2022 开始并跨越三个版本&#xff08;2022、2023、2025&#xff09;的开发过程的顶峰。 此次重大发布集中在六个核心主…

多机器人系统的大语言模型:综述

25年2月来自 Drexel 大学的论文“Large Language Models for Multi-Robot Systems: A Survey”。 大语言模型 (LLM) 的快速发展为多机器人系统 (MRS) 开辟新的可能性&#xff0c;从而增强通信、任务规划和人机交互。与传统的单机器人和多智体系统不同&#xff0c;MRS 带来独特…

【欧洲数据集】高分辨率网格气象数据集E-OBS

目录 数据概述最新版本 E-OBS 30.0e数据下载下载链接1:ECA&D官网下载链接2:ECMWF参考E-OBS 数据集(E-OBS, European high-resolution gridded dataset)是基于 European Climate Assessment & Dataset (ECA&D) 信息的高分辨率网格化观测数据集,涵盖欧洲地区的多…

游戏引擎学习第100天

仓库:https://gitee.com/mrxiao_com/2d_game_2 昨天的回顾 今天的工作重点是继续进行反射计算的实现。昨天&#xff0c;我们开始了反射和环境贴图的工作&#xff0c;成功地根据法线显示了反射效果。然而&#xff0c;我们还没有实现反射向量的计算&#xff0c;导致反射交点的代…

Mac上搭建宝塔环境并部署PHP项目

安装Docker Desktop》搭建Centos版本的宝塔环境》部署PHP项目 1. 下载Docker for mac 软件&#xff1a;https://www.docker.com/ 或使用终端命令&#xff1a;brew install --cask --appdir/Applications docker 2. 使用命令安装宝塔环境的centos7系统&#xff1a; docker pul…

从肠道菌群到炎症因子:读懂疾病的预警信号

当我们的皮肤被轻微割伤或烧伤时&#xff0c;伤口周围区域可能会变得红肿、发热&#xff0c;甚至伴有疼痛&#xff1b;感冒时&#xff0c;喉咙痛、肿胀&#xff1b;不小心扭伤后&#xff0c;可能会肿胀、疼痛和僵硬…这些都与炎症相关。 炎症&#xff0c;作为身体对损伤或感染的…

83.在 Vue3 中使用 OpenLayers 利用 TLE 计算并显示单个卫星的轨迹

1. 前言 在可视化开发中&#xff0c;卫星轨迹的实时计算与展示是一个比较有趣的应用场景。TLE&#xff08;Two-Line Element Set&#xff09;是一种用于描述卫星轨道参数的格式&#xff0c;我们可以通过 satellite.js 解析 TLE 数据&#xff0c;并计算卫星在任意时间点的位置。…

Vue3(2)

一.Vue新特性 &#xff08;1&#xff09;defineOptions:主要是用来定义Options API的选项 背景说明&#xff1a;有< script setup >之前&#xff0c;如果定义props&#xff0c;emits可以轻而易举地添加一个与setup平级 的属性。但是用了< script setup >后&#…

π 的奥秘:如何用有理数逼近无理数?

本文将围绕有理数、无理数、连续统以及它们之间的深刻联系展开讨论&#xff0c;并结合具体的数学理论如康托尔区间套定理、戴德金分割、柯西施瓦茨不等式等&#xff0c;进行简要探讨 由于本文并未深入探讨&#xff0c;可能存在部分不严谨的地方&#xff0c;也欢迎各位进行纠正…

图书管理项目(spring boot + Vue)

想要该项目的话&#xff0c;就 jia 我&#xff0c;并在评论区给我说一下&#xff0c;只需要1元&#xff0c;我把整个项目发给你 jia微&#xff1a;18439421203&#xff08;名字叫&#xff1a;Bingo&#xff09; 运行图片&#xff1a;

131,【2】 攻防世界 catcat-new

进入靶场 &#x1f431; 点击图片时发现url处很可疑 想到文件读取 ../app.py # 导入 os 模块&#xff0c;用于与操作系统进行交互&#xff0c;例如文件操作、路径操作等 import os # 导入 uuid 模块&#xff0c;用于生成通用唯一识别码&#xff0c;常用于生成随机的密钥 imp…

NO.12十六届蓝桥杯备战|关系操作符|操作符连用|浮点数比较|练习2道(C++)

关系操作符 关系操作符介绍 ⽤于⽐较的表达式&#xff0c;称为“关系表达式”&#xff08;relational expression&#xff09;&#xff0c;⾥⾯使⽤的运算符就称为“关 系运算符”&#xff08;relational operator&#xff09;&#xff0c;主要有下⾯6个。 运算符描述>⼤…

.NET Web-静态文件访问目录浏览

一、Web根目录访问 创建wwwroot文件夹app.UseStaticFiles(); // 启⽤静态⽂件中间件url/路径 进行访问 二、Web根目录之外的文件 app.UseStaticFiles(new StaticFileOptions {FileProvider new PhysicalFileProvider(Path.Combine(builder.Environment.ContentRootPath,&qu…

【漏洞复现】Casbin get-users 账号密码泄漏漏洞

免责声明 请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;作者不为此承担任何责任。工具来自网络&#xff0c;安全性自测&#xff0c;如有侵权请联系删除。…