【算法】基础算法002之滑动窗口(二)

👀樊梓慕:个人主页

 🎥个人专栏:《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C++》《Linux》《算法》

🌝每一个不曾起舞的日子,都是对生命的辜负


目录

前言

 5.水果成篮(medium)

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

7.串联所有单词的子串(hard) 

8.最小覆盖字串(hard)


前言

滑动窗口专题续作,本篇文章继续围绕滑动窗口进行讲解,并辅以实战OJ题帮助理解。


 欢迎大家📂收藏📂以便未来做题时可以快速找到思路,巧妙的方法可以事半功倍。 

=========================================================================

GITEE相关代码:🌟樊飞 (fanfei_c) - Gitee.com🌟

=========================================================================


 5.水果成篮(medium)

904. 水果成篮 - 力扣(LeetCode)https://leetcode.cn/problems/fruit-into-baskets/description/

你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。

你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:

  • 你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
  • 你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
  • 一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。

给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。

​ 阅读题目,其实就是找出一个最长的子数组的长度,要求子数组中不能超过两种元素。

思路:

  • 如果大小超过2:说明窗口内水果种类超过了两种。那么就从左侧开始依次将水果划出窗口,直到哈希表的大小小于等于2,然后更新结果;
  • 如果没有超过2:说明当前窗口内水果的种类不超过两种,直接更新结果ret。
     

 有了思路,画图独立完成代码,不要直接看博主的代码。

class Solution {
public:int totalFruit(vector<int>& fruits) {unordered_map<int, int> hash;int left = 0, right = 0;int ret = 0;int n = fruits.size();while (right < n){hash[fruits[right]]++;//进入窗口while (hash.size() > 2)//判断{hash[fruits[left]]--;//离开窗口if (hash[fruits[left]] == 0){hash.erase(fruits[left]);}left++;}ret = max(ret, right - left + 1);//更新结果right++;}return ret;}
};

但如果使用容器,我们需要频繁地erase元素,这就牺牲了一定的时间。

又因为题目说明元素个数是有限的:

​所以我们可以利用数组模拟一个哈希表,这样效率会显著提升。 

class Solution {
public:int totalFruit(vector<int>& fruits) {int hash[100001]={0};int left=0,right=0,kinds=0;int ret=0;int n=fruits.size();while(right<n){if(hash[fruits[right]]==0) kinds++;hash[fruits[right]]++;//进入窗口while(kinds>2)//判断{hash[fruits[left]]--;//离开窗口if(hash[fruits[left]]==0)kinds--;left++;}ret=max(ret,right-left+1);//更新结果right++;}return ret;}
};


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

438. 找到字符串中所有字母异位词 - 力扣(LeetCode)https://leetcode.cn/problems/find-all-anagrams-in-a-string/

给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。

不难发现,我们需要在字符串 s 中维护一个滑动窗口,且该滑动窗口的长度始终与字符串 p 相等。

然后依据该窗口内的元素构建哈希表与字符串 p 的哈希表作比较,如果两个哈希表相同,那么就证明滑动窗口内为字符串 p 的异位词。

那么如何比较两个哈希表是否相同呢?如题意:

​字符串 s 和 p 仅包含小写字母,所以我们只要遍历即可,时间复杂度为常数级,可以忽略。 

有了思路,画图独立完成代码,不要直接看博主的代码。 

class Solution {
public:vector<int> findAnagrams(string s, string p) {int hash1[26] = { 0 };for (auto e : p)hash1[e - 'a']++;int hash2[26] = { 0 };int left = 0, right = 0;int np = p.size();int ns = s.size();vector<int> ret;while (right < ns){hash2[s[right] - 'a']++;//进入窗口if (right - left + 1 > np)//判断hash2[s[left++] - 'a']--;//离开窗口int flag = 0;for (int i = 0; i < 26; i++)if (hash1[i] != hash2[i])flag = 1;if (flag == 0)ret.push_back(left);//更新结果right++;}return ret;}
};

可是如果 s 和 p 内不光存储小写字母,或者 s 和 p 是某种容器存储的是字符串,我们又该如何处理呢?如果还按照遍历的方式显然不现实,所以我们需要引入『 有效字符计数器count』。

  • 在每次『 进入窗口』之后,要维护count的值:如果该进入窗口的字符在 s哈希表 中的数目小于或等于 p哈希表 中的数目,那么就证明此时进入窗口的字符是 有效字符,count++;
  • 在每次『 离开窗口』之前,要维护count的值:如果该离开窗口的字符在 s哈希表 中的数目小于或等于 p哈希表 中的数目,那么就证明要离开窗口的字符是 有效字符,count--;
  • 每轮如果 有效字符数目与 p字符串长度相等,那么就证明此时 s字符串窗口内是 p字符串 的异位词,将left尾插到vector中。
class Solution {
public:vector<int> findAnagrams(string s, string p) {int hash1[26] = { 0 };for (auto e : p)hash1[e - 'a']++;int hash2[26] = { 0 };int left = 0, right = 0, count = 0;int np = p.size();int ns = s.size();vector<int> ret;while (right < ns){char in = s[right];if (++hash2[in - 'a'] <= hash1[in - 'a']) count++;//进入窗口后维护countif (right - left + 1 > np)//判断{char out = s[left++];if (hash2[out - 'a']-- <= hash1[out - 'a']) count--;//离开窗口前维护count}if (count == np)ret.push_back(left);//更新结果right++;}return ret;}
};

7.串联所有单词的子串(hard) 

30. 串联所有单词的子串 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/substring-with-concatenation-of-all-words/

给定一个字符串 s 和一个字符串数组 words words 中所有字符串 长度相同

 s 中的 串联子串 是指一个包含  words 中所有字符串以任意顺序排列连接起来的子串。

  • 例如,如果 words = ["ab","cd","ef"], 那么 "abcdef", "abefcd""cdabef", "cdefab""efabcd", 和 "efcdab" 都是串联子串。 "acdbef" 不是串联子串,因为他不是任何 words 排列的连接。

返回所有串联子串在 s 中的开始索引。你可以以 任意顺序 返回答案。

其实本题就是第6题的升级版,只不过第6题的原子是字符,本题的原子是字符串,因为给定的容器words内存储的字符串都是等长的。

所以整体的思路与第6题是完全相同的,只不过需要处理一些细节。


细节一:

执行一次滑动窗口逻辑不能包括所有情况,因为我们把字符串看作一个原子处理,但是字符串由一个个字符构成,一个字符串内的部分字符可以和另一个字符串的部分字符组成新的字符串,所以我们需要充分考虑所有情况,经过观察发现我们需要执行 字符串原子的长度次len 就能包含所有情况,比如:

这反映到left和right开始的位置。 


细节二:

结束条件应为 right + 原子字符串长度len > 字符串长度 。

因为如果大于,right再往后就够不成原子字符串了。


 细节三:

right 与 left 每次移动 原子字符串长度len,而不是1。


细节四:

『 判断』条件应为 right - left + 1 > 原子字符串长度len *  words中的字符串个数。


有了思路,画图独立完成代码,不要直接看博主的代码。 

class Solution {
public:vector<int> findSubstring(string s, vector<string>& words) {unordered_map<string,int> hash1;for(auto& e: words)hash1[e]++;int ns=s.size();int nw=words.size();int len=words[0].size();vector<int> ret;for(int i=0;i<len;i++)//细节1{int left=i,right=i,count=0;unordered_map<string,int> hash2;//维护窗口内单词的频次while(right + len <= ns)//细节2{//进入窗口+维护countstring in = s.substr(right,len);hash2[in]++;if(hash2[in]<=hash1[in]) count++;//判断if(right-left+1 > len*nw) //细节4{//离开窗口+维护countstring out=s.substr(left,len);if(hash2[out]<=hash1[out]) count--;hash2[out]--;left+=len;//细节3}if(count==nw){ret.push_back(left);//更新结果}right+=len;//细节3}}return ret;}
};

到这代码还能进一步优化。


细节五:

维护count时,因为判断语句会被执行,所以如果进入窗口的字符串in在hash1中不存在,那么in这个字符串就会加入到hash1中,这无疑是一种浪费,所以在比较之前,我们可以判断一下in是否在hash1中,如果不在那也就没有比较的必要了,out那块同理。

class Solution {
public:vector<int> findSubstring(string s, vector<string>& words) {unordered_map<string,int> hash1;for(auto& e: words)hash1[e]++;int ns=s.size();int nw=words.size();int len=words[0].size();vector<int> ret;for(int i=0;i<len;i++)//细节1{int left=i,right=i,count=0;unordered_map<string,int> hash2;//维护窗口内单词的频次while(right + len <= ns)//细节2{//进入窗口+维护countstring in = s.substr(right,len);hash2[in]++;if(hash1.count(in) && hash2[in]<=hash1[in]) count++;//细节5//判断if(right-left+1 > len*nw) //细节4{//离开窗口+维护countstring out=s.substr(left,len);if(hash1.count(out) && hash2[out]<=hash1[out]) count--;//细节5hash2[out]--;left+=len;//细节3}if(count==nw){ret.push_back(left);//更新结果}right+=len;//细节3}}return ret;}
};

8.最小覆盖字串(hard)

76. 最小覆盖子串 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/minimum-window-substring/description/

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。

注意:

  • 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
  • 如果 s 中存在这样的子串,我们保证它是唯一的答案。

 同样的我们最先想到暴力枚举+哈希表的办法,但我们可以观察得到一定的规律做优化。


第一个问题:

当right右移到满足覆盖的条件时,left左移,right是否需要回退呢?

其实不需要,因为中间的元素我们是知道的,只需要在left左移时判断right是否需要移动即可。

  • 当left右移后,如果窗口内还满足覆盖条件,那么就证明right此时可以不动;
  • 当left右移后,如果窗口内不满足覆盖条件,那么就证明right要右移寻找新的满足条件的字符。

 而且根究上面的进出窗口,我们可以知道出窗口之前,即left右移之前,此时窗口内是满足条件的字符串,所以『 更新结果』要在『 离开窗口』之前完成。

并且如果窗口内一直满足覆盖条件,那么就应该一直出窗口,直到不满足覆盖条件为止,所以这里应该用while。


第二个问题:

如何判断是否满足覆盖条件,我们之前说利用哈希表,但两个哈希表又如何判断相等呢?

之前的题目相信对你有所启发,我们是利用了一个 有效字符计数器count 来判断,在这里我们同样可以利用这个 count,但唯一不同的是,此 count 要统计的是字符种类,而不是字符数,因为题目说了,对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。

所以进入窗口之后维护count的条件,应该是哈希表中对应字符的个数相等,此时才证明恰好覆盖。


 有了思路,画图独立完成代码,不要直接看博主的代码。 

class Solution {
public:string minWindow(string s, string t) {int hash1[128]={0};int kinds=0;for(auto ch : t){if(hash1[ch]==0)//统计有效字符有多少种kinds++;hash1[ch]++;}int left=0,right=0,count=0;int ns=s.size();int nt=t.size();int hash2[128]={0};int minLen=INT_MAX,begin=-1;while(right<ns){//进入窗口+维护countchar in=s[right];if(++hash2[in]==hash1[in]) count++;while(count==kinds)//判断{if(right-left+1 <minLen)//更新结果{minLen=right-left+1;begin=left;}//离开窗口+维护countchar out=s[left++];if(hash2[out]==hash1[out]) count--;hash2[out]--;}right++;}if(begin==-1) return "";else return s.substr(begin,minLen);}
};

滑动窗口专题结束,接下来是『 二分算法』


=========================================================================

如果你对该系列文章有兴趣的话,欢迎持续关注博主动态,博主会持续输出优质内容

🍎博主很需要大家的支持,你的支持是我创作的不竭动力🍎

🌟~ 点赞收藏+关注 ~🌟

=========================================================================

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

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

相关文章

【Java EE初阶十八】网络原理(三)

3. 网络层 网络层要做的事情主要是两方面&#xff1a; 1)、地址管理&#xff1a;制定一系列的规则,通过地址,描述出网络上一个设备的位置&#xff1b; 2)、路由选择&#xff1a;网络环境是比较复杂的&#xff0c;从一个节点到另一个节点之间&#xff0c;存在很…

达梦数据库——数据迁移sqlserver-dm报错问题_未完待续

记录SQL server到达梦数据迁移过程中遇到的问题&#xff0c;持续更新中... 报错情况一&#xff1a;Sql server迁移达梦连接报错’驱动程序无法通过使用安全套接字Q层(SSL)加密与SQL Server 建立安全连接。错误:“The server selected protocol version TLS10 is not accepted b…

每日一题 (不用加减乘除做加法,找到数组中消失的数字)

不用加减乘除做加法_牛客题霸_牛客网 (nowcoder.com) 可以使用位运算符实现两个整数的加法&#xff1a; 在二进制加法中&#xff0c;我们通常使用“逐位相加”的方法来模拟常规加法的过程。当两个数字进行加法运算时&#xff0c;从最低位&#xff08;通常是右侧&#xff09;开…

软考学习--计算机组成原理与体系结构

计算机组成原理与体系结构 数据的表示 进制转换 R 进制转换为 10 进制–按权展开法 10进制转换为2进制 原码 反码 补码 移码 原码 &#xff1a;数字的二进制表示反码 &#xff1a; 正数的反码等于原码&#xff0c;负数的反码等于原码取反补码&#xff1a; 正数的补码等…

【算法】C语言使用qsort对字符串字符进行排序(解决字符串长度不统一的问题)

核心思想是 当a的长度>b时&#xff0c;a一定比b大当a,b长度相等时&#xff0c;通过strcmp比较哪个字典序大&#xff0c;也就是实际的数哪个大当a的长度&#xff1c;b是&#xff0c;b一定比a大 这时候就不得不感慨C的string是多么好用的&#xff0c;哎 #include<stdio.h…

⭐北邮复试刷题LCR 034. 验证外星语词典__哈希思想 (力扣119经典题变种挑战)

LCR 034. 验证外星语词典 某种外星语也使用英文小写字母&#xff0c;但可能顺序 order 不同。字母表的顺序&#xff08;order&#xff09;是一些小写字母的排列。 给定一组用外星语书写的单词 words&#xff0c;以及其字母表的顺序 order&#xff0c;只有当给定的单词在这种外…

软考30-上午题-数据结构-小结

一、杂题汇总 真题1&#xff1a; 有向图——AOV 带权有向图——AOE 真题2&#xff1a; 二叉排序树&#xff1a;左子树< 根节点 < 右子树。 二叉排序树中序遍历&#xff0c;节点关键字有序&#xff08;递增&#xff09;&#xff1b; 关键字初始序列有序&#xff0c;二叉树…

ubuntu 之 zeitgeist-fts 占用内存

座右铭&#xff1a;怎么简单怎么来&#xff0c;以实现功能为主。 欢迎大家关注公众号与我交流 sudo chmod -x /usr/bin/zeitgeist-daemonsudo chmod -x /usr/bin/zeitgeist-datahublocate zeitgeist-ftssudo chmod -x /usr/lib/x86_64-linux-gnu/zeitgeist-fts # 使用 locate z…

数据可视化利器:五款必备工具推荐

在数据可视化的世界里&#xff0c;工具的选择往往决定了工作的效率和效果。作为一名资深的数据可视化用户&#xff0c;我尝试并使用了众多的数据可视化工具。今天&#xff0c;我想向大家推荐五款我认为最好用、最实用的数据可视化工具。 1. 山海鲸可视化 山海鲸可视化以其强大…

计数排序和归并排序

计数排序 计数排序是一种非比较排序。 原理&#xff1a; 代码&#xff1a; void CountSort(int* nums, int numsSize) {int max nums[0], min nums[0];for (int i 0; i < numsSize; i) {if (nums[i] > max) {max nums[i];}if (nums[i] < min) {min nums[i];}}…

黑马程序员-瑞吉外卖day9

菜品分类下拉列表 CategoryController里面写 /*** 根据条件查询分类数据** param category* return*/GetMapping("/list")ApiOperation("菜品分类目录")public R<List<Category>> list(Category category) {List<Category> list cate…

后端扫盲系列 - vue入门指南

vue特点 组件化&#xff1a;用户界面分解为可重用的组件&#xff0c;这些组件可以使开发的页面更加模块化和可维护双向数据绑定&#xff1a;vue提供了一种轻松绑定数据和DOM元素之间的机制&#xff0c;意味着数据发送变化时&#xff0c;视图会自动更新&#xff0c;反之亦然虚拟…

GptSoVits音频教程

这个号称5秒克隆&#xff0c;或者用1分钟音频训练10分钟就能达到原声效果。 5秒的号称&#xff0c;只要是&#xff0c;什么几秒的&#xff0c;大家可以完全不要想了&#xff0c;什么知更鸟&#xff0c;什么火山&#xff0c;包括本次的GptSoVits的效果肯定是不行的&#xff0c;…

数据结构通讲

目录 集合源码详解 一、常见数据结构讲解 1. 线性数据结构 1.1 数组 1.2 队列 1.3 链表 1.3.1 单向链表 1.3.2 双向链表 1.4 栈 2. 非线性数据结构 2.1 树 2.2 二叉树 2.2.1 概念介绍 2.2.2 遍历操作 2.2.3 删除节点 2.2.4 查找局限性 2.2.5 AVL&#xff08; …

【C深剖】typedef关键字

简介&#xff1a;本系列博客为C深度解剖系列内容&#xff0c;以某个点为中心进行相关详细拓展 适宜人群&#xff1a;已大体了解C语法同学 作者留言&#xff1a;本博客相关内容如需转载请注明出处&#xff0c;本人学疏才浅&#xff0c;难免存在些许错误&#xff0c;望留言指正 作…

智能高压森林应急消防泵特点及优势

随着科技的发展&#xff0c;消防设备也在不断地升级改进。智能高压森林应急消防泵作为一种新型的消防设备&#xff0c;其特点和优势日益凸显&#xff0c;为森林火灾的扑救提供了有力的支持。本文将从以下几个方面对智能高压森林应急消防泵的特点和优势进行阐述。 一、高强度耐腐…

Ubuntu本地安装code-server结合内网穿透实现安卓平板远程写代码

文章目录 1.ubuntu本地安装code-server2. 安装cpolar内网穿透3. 创建隧道映射本地端口4. 安卓平板测试访问5.固定域名公网地址6.结语 1.ubuntu本地安装code-server 准备一台虚拟机,Ubuntu或者centos都可以&#xff0c;这里以VMwhere ubuntu系统为例 下载code server服务,浏览器…

机器学习第二十八周周报 PINNs2

文章目录 week28 PINNs2摘要Abstract一、Lipschitz条件二、文献阅读1. 题目数据驱动的偏微分方程2. 连续时间模型3. 离散时间模型4.结论 三、CLSTM1. 任务要求2. 实验结果3. 实验代码3.1模型构建3.2训练过程代码 小结参考文献 week28 PINNs2 摘要 本文主要讨论PINN。本文简要…

天然肠衣市场分析:预计到2025年将达到45亿美元

肠衣作为包装香肠馅料的原材料一般分为可食用和不可食用两种&#xff0c;其中天然肠是由猪、羊等家畜多层肠道制作而成的&#xff0c;近些年来天然肠衣在全球市场需求不断增加。一、全球市场分析 1. 市场规模&#xff1a;根据市场研究机构的数据&#xff0c;2019年全球天然肠衣…

访问学者感谢信|人文社科工作者赴北欧访学

编者按&#xff1a;这位访问学者从委托我们申请到获得邀请函只用了一个月时间。为了表达感激之情&#xff0c;其当时就写了这封感谢信&#xff0c;但依据我们的惯例&#xff0c;一般是待申请者出国&#xff0c;一切安排妥当后再发成功案例。所以时至今日&#xff0c;才将该申请…