深度解析算法之滑动窗口

12滑动窗口—将 x 减到 0 的最小操作数

题目传送门

题目描述:

给你一个整数数组 nums 和一个整数 x 。每一次操作时,你应当移除数组 nums 最左边或最右边的元素,然后从 x 中减去该元素的值。请注意,需要 修改 数组以供接下来的操作使用。
如果可以将 x 恰好 减到 0 ,返回 最小操作数 ;否则,返回 -1 。

示例 1:

输入 nums = [1,1,4,2,3], x = 5
输出: 2
解释 最佳解决方案是移除后两个元素,将 x 减到 0 。

示例 2:
输入 nums = [5,6,7,8,9], x = 4
输出:-1

示例 3:

输入 nums = [3,2,20,1,1,3], x = 10
输出: 5
解释: 最佳解决方案是移除后三个元素和前两个元素(总共 5 次操作),将 x 减到 0 。

这里我们通过示例1里面的数据,我们可以发现最优的操作次数是2次image.png

对于示例2来说的话,我们不论是从左边还是右边进行删除操作,我们的x永远不可能到0的,所以我们返回-1

现在我们将问题进行转换:原来要求的是我们从最左边或者是最右边进行x删除元素,直到x变成0。那么我们可以将这个进行区域划分,如下图,左边的区域是a,右边的区域是b,a区域和b区域里面的数值加起来等于x,那么我们中间剩下的就是我们整个数组的值减去x,题目让我们求出最小的操作次数,那么就是说我们a+b最短的情况,换种思路,就是中间区域最长的情况,即sum-x,我们只要求出整个数组中和为sum-x的区域的最长值就行了
image.png
找出最长的子数组的长度,子数组的所有元素的和正好等于sum-x

做这种题我们可以想对立面

我们可以先想想 暴力解法:我们设置两个指针,左和右,我们让两个指针一开始指向起点,然后让右指针进行移动操作,直到我们的两个指针的区域间的值大于等于sum-x,我们就更新下我们的操作次数,然后移动我们的左指针继续进行更新的操作image.png
当我们的左指针往右边移动了,那么我们的右指针就没有必要往左边进行移动了,因为我们左指针向右移动过后,我们的两个指针区间中的元素大小更小了,我们右指针只能往右移动

那么我们现在在这个暴力解法的基础上进行优化
image.png

class Solution {public:int minOperations(vector<int>& nums, int x){int sum =0;for(int a:nums){sum+=a;}int target =sum-x;//细节问题,target的值不能小于0if(target<0) return -1;int ret=-1;for(int left=0,right=0,tmp=0;right<nums.size();right++){//tmp来计算我们左右指针中间的值的大小的tmp+=nums[right];//进窗口的操作while(tmp>target)//进行判断,如果区间中的值大于了target我们就停下来{tmp-=nums[left];left++;//出窗口}if(tmp==target)//更新结果{//更新我们两个指针中间的长度ret=max(ret,right-left+1);}}if(ret==-1) return ret;//没有找到else return nums.size()-ret;//找到的话我们直接返回我们的这个总长度减去我们中间的值}};

13滑动窗口—水果成篮

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

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

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

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

示例 1:

输入: fruits = [1,2,1 ]
输出 3
解释: 可以采摘全部 3 棵树。

示例 2:

输入: fruits = [0,_1,2,2]
输出: 3
解释: 可以采摘 [1,2,2] 这三棵树。
如果从第一棵树开始采摘,则只能采摘 [0,1] 这两棵树。

示例 3:

输入: fruits = [1,_2,3,2,2]
输出: 4
解释: 可以采摘 [2,3,2,2] 这四棵树。
如果从第一棵树开始采摘,则只能采摘 [1,2] 这两棵树。

示例 4:

输入: fruits = [3,3,3,1,2,1,1,2,3,3,4]
输出: 5
解释: 可以采摘 [1,2,1,1,2] 这五棵树。

我们现在有两个篮子进行摘水果,然后每个篮子只能装一个水果,不能装多种水果,我们现在按实例2来说,示例二我们先采摘0号水果,然后再采摘1号水果,然后我们就不能采摘了,因为再后面的2号水果和1号水果种类不同,所以我们是只能采摘2棵树,但是我们如果从1号开始采摘的话,我们然后可以摘2号水果,我们再还能接着采摘2号水果,所以我们这种方案是可以采摘3棵树的

就是我们的两个篮子只能装两种类型的水果,就是一个区间只能存在两种数字,不能存在第三种数字

转化: 找出一个最长的子数组,子数组里面不超过两种类型的水果

暴力解法:直接将所有的子数组都给找出来,找出其中最长的一个
我们可以借助一个哈希表来记录我们当前出现水果种类的次数

我们现在定义两个指针,左和右,两个指针都从最开始的位置行动,右指针保持运行,直到我们水果的种类大于2了,我们就停下来,那么此时左和右中间的区域就是我们要求的
然后我们的左指针往右运行,那么我们就得想想我们的右指针回不回来呢?是不是和左指针重新开始还是说直接从上次的位置开始
我们的左指针往右运行,那么我们水果种类只能出现两种:不变或者是变小,绝不可能变大,因为我们左往右走了一步,水果少了一个

如果这个在左指针运行后,我们水果的种类不变的话,我们右指针就没必要品回来了

如果水果种类减少的话,那么我们区间里面还是只存在一种类型的结果,我们的右指针也是没有必要回去的,右指针直接右移image.png
那么我们就可以使用滑动窗口进行解答了

进窗口的操作就是将我们右指针指向的元素丢到哈希表中
如果这个哈希表的长度大于2的话,我们就进行出窗口的操作
在哈希表中,我们不仅仅存入这个水果的种类,还要存入水果的数量

在出窗口的时候我们还要对我们哈希表中的元素进行判断,如果某种种类的水果数量变成了0的话,那么我们要将这个水果从哈希表中删除的

下面是我们的代码

class Solution {public:int totalFruit(vector<int>& f){unordered_map<int ,int>hash;//统计窗口内出现多少种水果int ret=0;for(int left=0,right=0;right<f.size();right++){hash[f[right]]++;//进窗口while(hash.size()>2)//进行判断,查看我们哈希表中的水果种类是否大于2{//出窗口操作hash[f[left]]--;//因为我们当前left要往右边走,那么我们就得判断下我们这个减少数量的这个水果的个数是否变成了0if(hash[f[left]]==0)//如果这个水果数量变成了0的话,那么我们就得将这个水果从哈希表中删除了{hash.erase(f[left]);//将这个种类变为0的水果从哈希表中删除}left++;//左指针往右边走}ret=max(ret,right-left+1);//更新结果,结果就是我们左右指针中间的区间的长度}return ret;}};

image.png
但是我们发现我们的这个时间复杂度不行,因为我们哈希表中插入删除元素
所以我们这里是可以进行优化的操作的,对当前的哈希表进行优化操作

我们可以直接利用一个数组来创建哈希表,并且增加一个变量kinds来表示我们水果的种类
下面我们就是直接利用数组进行操作

class Solution {public:int totalFruit(vector<int>& f){int hash[100001]={0};//统计窗口内出现多少种水果int ret=0;for(int left=0,right=0,kinds=0;right<f.size();right++){//如果当前right指针指向的水果的个数为0的话,我们就种类++if(hash[f[right]]==0) kinds++;//维护水果的种类hash[f[right]]++;//进窗口while(kinds>2)//进行判断,查看我们哈希表中的水果种类是否大于2{//出窗口操作hash[f[left]]--;//因为我们当前left要往右边走,那么我们就得判断下我们这个减少数量的这个水果的个数是否变成了0if(hash[f[left]]==0)//如果这个水果数量变成了0的话,我们直接让种类减一{kinds--;}left++;//左指针往右边走}ret=max(ret,right-left+1);//更新结果,结果就是我们左右指针中间的区间的长度}return ret;}};

14滑动窗口_找到字符串中所有字母异位词

题目链接

给定两个字符串 s 和 p,找到 s 中所有 p 的

异位词

的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

示例 1:

输入: s = “cbaebabacd”, p = “abc”
输出: [0,6]
解释:
起始索引等于 0 的子串是 “cba”, 它是 “abc” 的异位词。
起始索引等于 6 的子串是 “bac”, 它是 “abc” 的异位词。

示例 2:

输入: s = “abab”, p = “ab”
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 “ab”, 它是 “ab” 的异位词。
起始索引等于 1 的子串是 “ba”, 它是 “ab” 的异位词。
起始索引等于 2 的子串是 “ab”, 它是 “ab” 的异位词

我们需要快速判断两个字符串是异位词,我们可以通过比较两个字符串来比较判断是否是异位词
image.png
操作方式就是利用哈希表来进行计数
下方就是暴力解法:不断的比较
image.png

一次移动len个子串然后和另一个子串进行比较,那么我们这里是可以用滑动窗口进行解答的
image.png

利用count来统计窗口中有效字符的个数
进窗口后我们考虑下我们进窗口的这个数据是否是有效的数据,这个字母的出现个数是否个另一个字符串里面对应字母的出现次数相等

出窗口之前我们判断下我们出去这个字符是否是有效字符,是有效字符的话那么我们就进行count–操作

然后我们更新结果

class Solution {public:vector<int> findAnagrams(string s, string p){vector<int >ret;int hash1[26]={0};//统计字符串p中每个字符出现的个数for(auto ch:p){hash1[ch-'a']++;//对应字符的下标,统计对应字母出现的次数}int hash2[26]={0};//统计窗口里面的每一个字符出现的个数int m=p.size();for(int left=0,right=0,count=0;right<s.size();right++){char in =s[right];//进窗口hash2[in-'a']++;//统计对应字符出现的个数if(hash2[in-'a']<=hash1[in-'a']) count++;//如果s中的这个字符的出现次数小于p中字符出现的个数的话,那么有效字符要++的if(right-left+1>m)//判断,这个就是窗口大了,我们得进行出窗口的操作{//出窗口&&维护countchar out=s[left++];//将这个元素丢出去,将这个元素丢出去,我们的left往右走if(hash2[out-'a']<=hash1[out-'a']) count--;//如果满足条件的话,那么就说明当前滑出去的是一个有效字符hash2[out-'a']--;}//更新if(count==m) ret.push_back(left);//直接将起始位置的下标进行输出}return ret;}};

说白了就是一直维持这个count的大小,然后知道count的大小等于这个p字符串的长度了,我们就将起始的左指针进行返回的操作

15滑动窗口—串联所有单词的子串

题目链接

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

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

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

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

示例 1:

输入: s = “barfoothefoobarman”, words = [“foo”,“bar”]
输出:[0,9]
解释: 因为 words.length == 2 同时 words[i].length == 3,连接的子字符串的长度必须为 6。
子串 “barfoo” 开始位置是 0。它是 words 中以 [“bar”,“foo”] 顺序排列的连接。
子串 “foobar” 开始位置是 9。它是 words 中以 [“foo”,“bar”] 顺序排列的连接。
输出顺序无关紧要。返回 [9,0] 也是可以的。

示例 2:

输入: s = “wordgoodgoodgoodbestword”, words = [“word”,“good”,“best”,“word”]
输出 []
解释: 因为 words.length == 4 并且 words[i].length == 4,所以串联子串的长度必须为 16。
s 中没有子串长度为 16 并且等于 words 的任何顺序排列的连接。
所以我们返回一个空数组。

示例 3:

输入: s = “barfoofoobarthefoobarman”, words = [“bar”,“foo”,“the”]
输出:[6,9,12]
解释: 因为 words.length == 3 并且 words[i].length == 3,所以串联子串的长度必须为 9。
子串 “foobarthe” 开始位置是 6。它是 words 中以 [“foo”,“bar”,“the”] 顺序排列的连接。
子串 “barthefoo” 开始位置是 9。它是 words 中以 [“bar”,“the”,“foo”] 顺序排列的连接。
子串 “thefoobar” 开始位置是 12。它是 words 中以 [“the”,“foo”,“bar”] 顺序排列的连接。

我们可以将题目进行转换,在左边的字符串中找到我们右边的a b的异位词就行了,那么这个题就很像之前的那个找到字符串中所有字母异位词这个题了
image.png
找到一个字符串就返回起始位置

那么这个题我们就会使用到滑动窗口和哈希表了

相较于找到字符串中所有字母异位词这个题的话,我们有三点不同

  • 哈希表
    之前的那题我们里面的哈希表存储的是一个个的字符,这里的话我们是一个又一个的字符串

  • left和right指针的移动
    因为我们字符串数组里面的字符串的长度都是相等的,那么我们在移动的时候为了让新的字符串进入到窗口的话,那么我们每次移动的话是需要前进len个距离,len就是我们单个字符串的长度
    移动的步长是每个单词的长度

  • 滑动窗口的执行次数
    我们滑动窗口执行的次数是len次
    因为我们的指针不仅仅是从第一个位置开始的,也可能从第二个位置,甚至第len个位置,如果从len+1的位置开始的话,那么就是重复了,和从第一个位置开始的情况重叠了,所以我们进行滑动窗口的次数是len次
    image.png

class Solution {public:vector<int> findSubstring(string s, vector<string>& words){vector<int> ret;//创建一个哈希表并且将单词在word中出现的次数进行记录unordered_map<string, int> hash1;;//保存words里面所有的单词的频次for(auto& s:words) hash1[s]++;//记录words里面所有的单词出现的频次//计算单词的长度int len=words[0].size(),m=words.size();//多少个单词//我们这里的滑动窗口是需要执行len次的for(int i=0;i<len;i++)//执行len次滑动窗口{unordered_map<string,int>hash2;//维护滑动窗口内单词的频次//因为我们right在移动到对应的位置后,我们需要将right+len个字符放到滑动窗口里面去for(int left=i,right=i,count=0;right+len<=s.size();right+=len)//count就是有效字符串的个数了{//进窗口+维护countstring in =s.substr(right,len);//直接获取right后面len个字符,那么in就是一个字符串了hash2[in]++;//我们将in这个字符出现的频次进行更新if(hash2[in]<=hash1[in]) count++;//如果我们hash2这个哈希中当前字符串出现的频次小于hash1的话,说明是有效单词,如果我们哈希2里面这个字符串出现次数大于哈希1的话,那么这个肯定是一个无效的单词,因为hash1中都没有进行记录//判断if(right-left+1>len*m)//如果我们当前左右指针的范围大于我们要找的子串的长度的话,那么我们就进行出窗口操作{//出窗口,维护countstring out=s.substr(left,len);//我们将left后面三个单词的范围的这个字符串拿出来if(hash2[out]<=hash1[out]) count--;//有效单词减一hash2[out]--;//出完窗口之后,我们的left需要向后移动left+=len;}//更新结果if(count==m) ret.push_back(left);//将我们}}//当整个循环结束之后,我们ret中存储的就是结果了return ret;}};

我们先创建一个哈希1,用来存储我们words中的单词出现的次数,利用范围for获得每个单词出现的频次

然后我们再计算len就是每个单词的长度,因为这个题中每个单词的长度都是一样的
然后我们再来一个for循环,次数就是len次,因为我们的左指针有效循环的次数就是len次,如果是len+1次的话,那么结果是和第一次的结果是相同的

在这个for循环中我们再来一个for循环进行进窗口的操作,在进窗口之前我们需要创建一个哈希2来记录我们滑动窗口内单词的频次

这个内循环的我们需要维护好count,就是窗口内有效单词的数量,如果没有在哈希1中出现过的,统统都是无效单词,出现过的才算是有效单词

然后我们进行进窗口的操作,利用substr获取我们right后面len个范围的单词,将这个字符串放到in中,然后给hash2记录这个单词出现的次数,我们在进窗口后我们维护好我们的count,如果当前的单词在hash2中出现的次数小于hash1中出现的次数的话,那么就证明这个单词是有效的,为什么呢?因为如果hash2中这个单词出现的次数大于hash1中的话,那么这个单词就是无效的,在hash1中不存在的,所以hash2中出现次数小于等于hash1中单词的次数的话,那么在这个单词就是有效的单词,那么我们count++

然后我们进行判断,如果我们当前的right和left的范围大于我们words中字符串组合成的字符串的长度的话,那么我们就进行出窗口的操作,我们就得将我们的left往右边进行移动了,还是利用substr获取我们left后面len个字符的字符串,将这个字符串存在out中,然后将hash2中的out这个字符串给减一,在减一之前我们还需要维护下我们的count,对我们这个单词进行判断,是否是有效的单词

出完窗口后,我们的left需要往右边进行移动,移动的距离是len
然后我们进行结果的更新,如果我们count=m的话,那么我们将此时的left,就是这个对应字符串的开始位置存储子啊我们的vector容器中去

最后直接返回了

但是吧,我们当前的这个效率比较慢,因为我们在判断当前的字符串是否是有效的字符串的时候,如果我们这个字符串in在hash1中不存在 的话,那么我们就得进行创建hash1[in]了,所以这里是存在效率变低的,所以我们可以在判断条件中加上hash1.count(in)来判断我们的这个单词的数量,如果是0的话,我们 直接退出了这个条件判断了

class Solution {public:vector<int> findSubstring(string s, vector<string>& words){vector<int> ret;//创建一个哈希表并且将单词在word中出现的次数进行记录unordered_map<string, int> hash1;;//保存words里面所有的单词的频次for(auto& s:words) hash1[s]++;//记录words里面所有的单词出现的频次//计算单词的长度int len=words[0].size(),m=words.size();//多少个单词//我们这里的滑动窗口是需要执行len次的for(int i=0;i<len;i++)//执行len次滑动窗口{unordered_map<string,int>hash2;//维护滑动窗口内单词的频次//因为我们right在移动到对应的位置后,我们需要将right+len个字符放到滑动窗口里面去for(int left=i,right=i,count=0;right+len<=s.size();right+=len)//count就是有效字符串的个数了{//进窗口+维护countstring in =s.substr(right,len);//直接获取right后面len个字符,那么in就是一个字符串了hash2[in]++;//我们将in这个字符出现的频次进行更新if(hash1.count(in)&&hash2[in]<=hash1[in]) count++;//如果我们hash2这个哈希中当前字符串出现的频次小于hash1的话,说明是有效单词,如果我们哈希2里面这个字符串出现次数大于哈希1的话,那么这个肯定是一个无效的单词,因为hash1中都没有进行记录//判断if(right-left+1>len*m)//如果我们当前左右指针的范围大于我们要找的子串的长度的话,那么我们就进行出窗口操作{//出窗口,维护countstring out=s.substr(left,len);//我们将left后面三个单词的范围的这个字符串拿出来if(hash1.count(out)&&hash2[out]<=hash1[out]) count--;//有效单词减一hash2[out]--;//出完窗口之后,我们的left需要向后移动left+=len;}//更新结果if(count==m) ret.push_back(left);//将我们}}//当整个循环结束之后,我们ret中存储的就是结果了return ret;}};

16滑动窗口—最小覆盖子串

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

注意:

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

示例 1:

输入: s = “ADOBECODEBANC”, t = “ABC”
输出:“BANC”
解释: 最小覆盖子串 “BANC” 包含来自字符串 t 的 ‘A’、‘B’ 和 ‘C’。

示例 2:

输入: s = “a”, t = “a”
输出:“a”
解释: 整个字符串 s 是最小覆盖子串。

示例 3:

输入: s = “a”, t = “aa”
输出: “”
解释: t 中两个字符 ‘a’ 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。

只要我们的s中可以将t全部涵盖了的话,那么就是符合要求的
使用暴力解决,如果hash2中的ABC的次数大于hash1中的次数的话,那么hash2里面存的这一段字符串就是有效的字符串了

image.png
然后接下来从D这个位置接着枚举,就这种暴力解决的方法

解法一:暴力枚举+哈希表

那么我们是否可以在这个解法的基础上进行优化呢
我们在找到符合要求的一组之后,我们的left往右走,那么此时我们的right是没有必要回去的
image.png
如果我们原先left指向的元素他的出现次数太多了或者是一个无效的,那么我们的left往右边移动一位,和此时right组成的区间依旧是有效的

但是如果我们原先left指向的元素是一个有效字符并且出现次数的小于我们hash1中的次数,那么我们此时的区间就是不符合要求的,我们的right依旧不用回来,继续向后面走,直到找到了符合要求的那个元素为止

那么优化的方案就是滑动窗口+哈希表

当我们的窗口合法之后我们再进行出窗口的操作image.png
优化操作
我们使用count标记有效字符的种类 ,我们这里需要hash2中出现hash1中所有种类的字符,而不是个数,我们一定要按照有效字符种类来进行操作

我们进窗口的时候维护一下count,出窗口的时候维护一下count,判断的时候判断一下count

class Solution {public:string minWindow(string s, string t){int hash1[128]={0};//统计t字符串中每一个字符出现的频次int kinds=0;//统计有效字符有多少种for(auto ch:t){//如何判断我们当前的有效字符种类是否增加了呢?//如果在进窗口前这个字符数量为0 的话,并且是有效字符的话,那么我们的种类就多了一种了if(hash1[ch]==0) kinds++;//有效字符的种类加一hash1[ch]++;}//出了循环我们就统计到了我们字符的种类数量int hash2[128]={0};//统计窗口内每个字符的频次int minlen=INT_MAX,begin=-1;for(int left=0,right=0,count=0;right<s.size();right++){char in=s[right];//获取我们这个字符的信息hash2[in]++;//给这个字符的次数++//判断我们加入的字符是否是有效字符//进窗口+维护count变量if(hash2[in]==hash1[in]) count++;//说明我们加入了一个有效的字符//判断条件while(count==kinds){//更新结果if(right-left+1<minlen)//说明此时我们需要更新结果了{//计算最小的子串的长度minlen=right-left+1;//开始的指针指向我们的leftbegin=left;}//出窗口char out =s[left];//记录当前出窗口的单词left++;//左指针往右边走//维护countif(hash2[out]==hash1[out])count--;//说明我们当前的字符是有效字符hash2[out]--;//将当前出窗口的字符进行--操作}}if(begin==-1) return "";//如果我们开始的指针没有变的话,那么我们就直接返回空就行了else return s.substr(begin,minlen);//如果变了的话,我们begin后面minlen个范围的字符串获取然后返回就行了}};

我们先创建hash1来记录我们t中字符出现的次数,创建变量kinds记录种类,
然后我们遍历t,获取我们的种类的数量,并且记录字符出现的频次

然后我们创建hash2来记录我们窗口中的每个字符出行的频次
创建变量minlen记录我们当前最小的子串长度,以及开始的指针begin

然后我们进入到滑动窗口的操作,我们先获取我们right指针的位置上的元素,并且在hash2中记录出现的次数
然后我们在进窗口后判断下当前进的这个字符是否是有效的字符,并且进行维护我们的count(字符种类)
是否是有效的字符,我们依靠这段代码hash2[in]==hash1[in],如果我们hash2中进的这个字符的次数等于我们hash1中字符的次数的话,那么我们就判定当前字符是有效的
如果有效的话,那么我们当前的count就++

进行判断,如果我们的count的大小等于我们的kinds的话,就是说明我们当前的窗口就是一个有效的窗口了
那么我们就进行结果的更新操作
记录我们当前区间的长度:right-left+1 并且记录我们当前的begin

然后我们再进行出窗口的操作,我们记录我们当前left位置的元素,然后从hash2中减少次数
并且我们在减少次数之前我们需要判断下我们这个出窗口的元素是否是有效的字符,

最后出了循环,我们就返回对应的结果了

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

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

相关文章

[MySQL初阶]MySQL表的操作

MySQL表的操作 1. 创建表2. 查看表结构3. 修改表&#xff08;修改表的属性而非表的数据&#xff09;4. 删除表 1. 创建表 语法&#xff1a; CREATE TABLE table_name (field1 datatype,field2 datatype,field3 datatype ) character set 字符集 collate 校验规则 engine 存储…

sqlalchemy详细介绍以及使用方法

SQLAlchemy是一个Python的ORM&#xff08;对象关系映射&#xff09;工具&#xff0c;它允许开发者使用Python代码来操作数据库而不必直接编写SQL语句。SQLAlchemy提供了一种抽象层&#xff0c;使开发者可以通过简单的Python对象来表示数据库表和记录&#xff0c;从而实现对数据…

图解AUTOSAR_SWS_LINDriver

AUTOSAR LIN驱动详解文档 基于AUTOSAR标准的本地互联网络(LIN)驱动程序技术规范解析 目录 1. 概述 1.1 AUTOSAR LIN驱动简介1.2 LIN协议基础2. LIN驱动架构 2.1 类图结构2.2 状态机设计3. LIN帧结构 3.1 基本帧组成3.2 PID结构4. LIN驱动配置 4.1 主要配置参数4.2 配置结构5. L…

《网络管理》实践环节03:snmp服务器上对网络设备和服务器进行初步监控

兰生幽谷&#xff0c;不为莫服而不芳&#xff1b; 君子行义&#xff0c;不为莫知而止休。 应用拓扑图 3.0准备工作 所有Linux服务器上&#xff08;服务器和Agent端&#xff09;安装下列工具 yum -y install net-snmp net-snmp-utils 保证所有的HCL网络设备和服务器相互间能…

2025年内外网文件交换系统排名分析

在时代&#xff0c;企业的日常运营离不开内外网文件的交换。然而&#xff0c;传统的文件传输方式难以满足企业对多方面的要求。以下是一些备受关注的内外网文件交换系统及其排名分析。 第一名&#xff1a;阳途内外网文件交换系统 阳途内外网文件交换系统是一款专为解决内外网…

【Centos】centos7内核升级-亲测有效

相关资源 通过网盘分享的文件&#xff1a;脚本升级 链接: https://pan.baidu.com/s/1yrCnflT-xWhAPVQRx8_YUg?pwd52xy 提取码: 52xy –来自百度网盘超级会员v5的分享 使用教程 将脚本文件上传到服务器的一个目录 执行更新命令 yum install -y linux-firmware执行脚本即可 …

Qt进阶开发:QDirModel的使用

文章目录 一、QDirModel的基本介绍二、QDirModel的基本使用2.1 在 QTreeView 中显示文件系统2.2 在 QListView 显示当前目录2.3 在 QTableView 中使用 三、QDirModel的常用API1. 构造 & 目录操作1.1 创建 QDirModel1.2 设置根目录 2. 过滤 & 排序2.1 过滤文件类型2.2 设…

牛客 除2问题

除2&#xff01; 贪心堆 让偶数入堆 注意点&#xff1a; 1.判断堆是否为空再进行操作 2. 为了防止超时&#xff0c;我们采取先求和的方式&#xff0c;后面调整之后再减掉&#xff0c;可以节省一次遍历的时间。 3.注意数据范围&#xff0c;要用long long #include<iost…

#MySQL 语句大全(完整实用教程)

&#x1f4cc; MySQL 语句大全&#xff08;完整实用教程&#xff09; &#x1f4cc; 1. 数据库操作 ✅ 创建数据库 CREATE DATABASE mydb; -- 创建名为 mydb 的数据库✅ 使用数据库 USE mydb; -- 选择数据库✅ 删除数据库 DROP DATABASE mydb; -- 删除数据库&#xff08…

万字重谈C++——类和对象篇

什么是类&#xff1f; 在编程中&#xff0c;类是用来创建对象的模板。可以把类看作一个蓝图&#xff0c;它定义了对象的属性&#xff08;特征&#xff09;和方法&#xff08;行为&#xff09;。例如&#xff0c;如果我们有一个“学生”的类&#xff0c;它可能包含学生的名字、…

18认识Qt坐标系

平面直角坐标系(笛卡尔坐标系) 数学上的坐标系 右手坐标系 计算机中的坐标系 左手坐标系 坐标系的原点(0,0) 就是屏幕的左上角 /窗口的左上角 给 Qt 的某个控件,设置位置,就需要指定坐标.对于这个控件来说, 坐标系原点就是相对于父窗口/控件的. QPushButton 的父元素/父控件/父…

量子计算与人工智能的结合:未来科技的双重革命

引言 在过去几十年里&#xff0c;人工智能&#xff08;AI&#xff09;和计算能力的提升一直是推动科技进步的重要力量。然而&#xff0c;随着深度学习和大规模数据处理的发展&#xff0c;传统计算架构的算力瓶颈逐渐显现&#xff0c;人工智能的训练和推理效率受到了限制。在此背…

SEO长尾词优化策略精要

内容概要 长尾关键词优化是SEO策略中实现精准流量捕获的核心环节。本文从定位方法、搜索意图分析、词库构建三个维度切入&#xff0c;系统阐述如何通过数据化工具筛选高转化潜力词&#xff0c;并结合用户行为路径优化内容架构。具体而言&#xff0c;内容将覆盖关键词挖掘工具的…

基于大模型的主动脉瓣病变预测及治疗方案研究报告

目录 一、引言 1.1 研究背景 1.2 研究目的 1.3 研究意义 二、大模型预测主动脉瓣病变原理 2.1 大模型介绍 2.2 数据收集与处理 2.3 模型训练与优化 三、术前预测与评估 3.1 主动脉瓣病变类型及程度预测 3.2 患者整体状况评估 3.3 手术风险预测 四、术中应用与监测…

进程和内存管理

目录 一.进程的基本信息 1.1进程的定义 1.2进程的特征 1.3进程的组成 1.4线程产生的背景 1.5线程的定义 1.6进程与线程的区别 1.7进程的类别 1.8进程的优先级 1.8.1进程优先级的概念 1.8.2PRI和NI 1.9僵尸进程 1.9.1僵尸进程的定义 1.9.2僵尸进程产生的原因 1.9…

css动态设置div宽高,calc函数

在css中使用calc函数 calc() 是 CSS 中的一种函数&#xff0c;用于动态计算长度值。它允许你在 CSS 属性中进行数学运算&#xff0c;结合不同的单位&#xff08;如 px、%、em 等&#xff09;&#xff0c;从而创建更加灵活和响应式的布局 表达式规则 运算符&#xff1a;支持加…

飞浆PaddlePaddle 猫狗数据大战

猫狗数据大战 1 数据集的准备以及处理操作1.1 数据集1.2 文件解压操作&#xff08;python&#xff09; 1.3 数据的分类1.4 创建训练集和测试集 2 网络构建CNN版本--DeepID 人脸识别网络结构DeepID 与 CNN 网络结构的差异 3 深度学习模型训练和推理的核心设置4 制图5 训练6 预测…

Spring Boot后端开发全攻略:核心概念与实战指南

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家、全栈领域优质创作者、高级开发工程师、高级信息系统项目管理师、系统架构师&#xff0c;数学与应用数学专业&#xff0c;10年以上多种混合语言开发经验&#xff0c;从事DICOM医学影像开发领域多年&#xff0c;熟悉DICOM协议及…

PPT助手:一款集计时、远程控制与多屏切换于一身的PPT辅助工具

PPT助手&#xff1a;一款集计时、远程控制与多屏切换于一身的PPT辅助工具 &#x1f4dd;&#x1f3a4; 在现代化的演讲和演示中&#xff0c;如何高效地控制PPT进程、保证展示的流畅性与精准性&#xff0c;成为了每个演讲者必须面对的挑战。无论是商务汇报、学术演讲&#xff0…

WEB安全--文件上传漏洞--php伪协议的利用

一、伪协议介绍 1.1、内容 在 PHP 中&#xff0c;伪协议通常指的是一种通过特定的 URL 协议方案实现某些特殊功能或行为的方式。伪协议通常并不是标准的协议&#xff08;如 HTTP、HTTPS&#xff09;&#xff0c;而是由应用程序或开发者自定义的“伪”协议&#xff0c;用于执行…