算法:滑动窗口题目练习

目录

题目一:长度最小的子数组

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

题目三:最大连续 1 的个数III

题目四:将 x 减到 0 的最小操作数

题目五:水果成篮

题目六:找到字符串中所有字母异位词

题目七:串联所有单词的子串

题目八:最小覆盖子串


题目一:长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组

 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度如果不存在符合条件的子数组,返回 0 。

示例 1:

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

示例 2:

输入:target = 4, nums = [1,4,4]
输出:1

示例 3:

输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0

解法一:暴力枚举出所有的子数组的和

首先两层循环枚举所有的子数组,然后每一个子数组需要遍历一遍得到总和,所以它的时间复杂度是O(N^3)

这里的暴力枚举策略可以优化为:提前定义一个sum,表示子数组的和,每次right往后移动,都sum+=该值,所以省去了一次遍历子数组的过程,优化为了O(N^2)


解法二:利用单调性,使用“ 同向双指针 "来优化

这里的同向双指针就称为滑动窗口,两个指针都不会回退,向同一个方向滑动的过程,形象的看做一个滑动窗口,维护left到right的这段区间

第一个规律:

下面分析一下,因为给的数组nums的值都是正整数,所以sum一旦加right指向的数大于等于target了,那此时right就没必要往后再遍历了

因为当前已经大于等于target了,再往后加只会使得数组的长度越来越长,不符合条件

假设数组nums[2, 3, 1, 2, 4, 3],target=7,当2,3,1,2时已经满足了,此时len=4,那继续right右移,2,3,1,2,4也满足条件,只不过len=5,比4大,就pass了,所以没必要继续往后了,因为越往后len肯定越大的

我们利用单调性,规避了很多没有必要的枚举行为

第二个规律:

假设数组nums[2, 3, 1, 2, 4, 3],target=7,当2,3,1,2加起来已经等于8,满足条件了,此时left指向的是2,right指向的是2(第4个数字)

该left++了,此时sum并不需要清0,从头加,因为我们知道2+3+1+2=8,那么left++就表示从第二个数字3开始,所以此时指向的子数组的和sum只需要-2即可,不需要重新加一遍从left到right的值

滑动窗口的使用:
①left= 0, right= 0
②进窗口
③判断什么时候出窗口

时间复杂度是O(N),因为虽然代码是两层循环,但是其实每次都是right右移或者left右移,相当于是n+n次,即2n次,也就是O(N)的时间复杂度


下面详细讲解上述的三步滑动窗口的使用过程:

假设数组nums[2, 3, 1, 2, 4, 3],target=7

第一步,left和right都是0,都指向2,sum初始值为0

第二步,right进入窗口,此时sum需要更新为2

第三步,此时判断sum的值是否满足要求,发现2 < 7,不满足,所以不出窗口,left不动,继续进窗口,也就是right右移,此时right指向3,sum更新为5,接下来继续判断:5 < 7,不满足,所以重复第二步

判断后不出窗口,left不动,继续进窗口,也就是right右移,此时right指向1,sum更新为6,判断6 < 7,不满足,所以重复第二步

判断后不出窗口,left不动,继续进窗口,也就是right右移,此时right指向2,sum更新为8,判断8 >= 7,满足条件,所以更新结果,长度len = 4

然后出窗口,因为此时已经满足条件了,就不用right继续右移了,直接出窗口left右移

出窗口left右移,sum更新为8-2=6,此时继续判断:6 < 7,不出窗口,left不动,right右移,此时right指向4,sum更新为10,满足条件,此时更新结果,len = 4不变

继续left右移,指向了1,sum更新为了7,此时继续判断:7 >= 7,满足条件,继续更新结果,len = 3

出窗口,left右移,指向2,sum更新为了6,判断:6 < 7,不满足

right右移指向3,sum更新为了9,9 >= 7,此时len = 3不变

继续left右移,指向了4,sum更新为7,此时继续判断:7 >= 7,满足条件,继续更新结果,len = 2

继续left右移,指向了3,sum更新为了3,不满足

此时本应该right右移,但是没有元素了,所以滑动窗口就结束了,返回结果len = 2,此题结束

上述便是滑动窗口的使用样例,比较详细,在后面的时候就不会再这么详细说明了,因为是第一道题所以详细列举一下用法,方便理解


代码如下:

class Solution {
public:int minSubArrayLen(int target, vector<int>& nums) {int len = INT_MAX, n = nums.size(), sum = 0;for(int left = 0, right = 0; right < n; right++){//进窗口sum += nums[right];while(sum >= target)//判断{len = min(len,right-left+1);//更新结果//出窗口sum -= nums[left++];}}//可能会出现没有结果的情况,判断len是否没改变return len == INT_MAX ? 0 : len;}
};

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

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串的长度。

示例 1:

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。请注意,你的答案必须是 子串 的长度,"pwke"是一个子序列,不是子串。

子串也就是连续的一段字符

解法一:暴力枚举 + 哈希表(判断字符是否重复出现)

暴力解法,也就是固定每一个起始位置,依次向后扩展,直到扩展到不能扩展为止,然后统计一下长度,把所有情况都枚举到,找一个最大值即可

这里的时间复杂度是O(N^2)的

解法二:利用滑动窗口来解决问题

我们发现,如果是"dabcabcbb",当left指向d,right指向a后,遇到了重复字符,此时不需要left++,然后right再从left的位置重新++

因为已经知道dabc是不重复的,所以如果left再++指向a,right依然会遇到a停止,此时的子串长度肯定是比上一次小的
所以优化一:可以让left先跳过这个重复字符,再继续计算不重复子串长度

因为left已经跳过这个重复字符了,所以right与left之间不会再有重复字符了,所以right就不需要再指向left的位置重新++

所以优化二:当left跳过重复字符时,right就可以继续从当前位置向后寻找不重复字符

所以步骤依然是:进窗口、判断什么时候出窗口、更新结果(这里的更新结果的顺序因题目而定)

进窗口:让字符进入哈希表
判断:窗口内是否出现重复字符
更新结果:如果没有出现重复字符,此时就可以更新结果
出窗口:最后如果发现有重复字符,就从哈希表中删除字符


代码如下:

class Solution {
public:int lengthOfLongestSubstring(string s) {int n = s.size(), left = 0, right = 0, len = 0;int hash[128] = {0};while(right < n){hash[s[right]]++; // 进窗口while(hash[s[right]] > 1) // 判断是否出窗口hash[s[left++]]--; // 出窗口len = max(len, right-left+1); // 更新结果right++; // 下一个元素进窗口}return len;}
};

题目三:最大连续 1 的个数III

给定一个二进制数组 nums 和一个整数 k,如果可以翻转最多 k 个 0 ,则返回 数组中连续 1 的最大个数 。

示例 1:

输入:nums = [1,1,1,0,0,0,1,1,1,1,0], K = 2
输出:6
解释:[1,1,1,0,0,1,1,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 6。

示例 2:

输入:nums = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3
输出:10
解释:[0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 10。

这个题的题意很容易理解,也就是给一个K,可以将nums数组中最多K个0变为1(小于等于K),求最长的子数组长度

可以将这个问题转化为:找出最长的子数组,0的个数不超过k个

解法一:暴力枚举 + zero计数器(统计0的个数)

固定一个起点,依次枚举终点

例如nums = [1,1,1,0,0,0,1,1,1,1,0],K = 2,left指向1,right指向1,right遇到1无视,遇到0,zero++,直到zero > K为止,即走到下图所示的情况,right停止,红线即为当前情况连续最大的1个数,记录下来:

此时left++,right指向left的位置,重新向后移动,重复之前的操作,走到下图为止:

以此类推,直到right超过数组的长度,取之前记录下来的最长值,即为最终结果


解法二:滑动窗口 + zero计数器(统计0的个数)

通过暴力解法,我们可以发现,当zero > K时,left如果没有走过0的位置,那么right每次都会停到刚刚停下的位置,所以可以做以优化:
当zero > K时,left超过一个0,zero--后,right再往后走,进而提高效率

也就是当这种情况时:

我们只需让left一直++,直到超过一个0为止:

此时right再往后走,循环上述步骤,直到right越界为止

滑动窗口的步骤如下:

①left=0;right=0
②进窗口
③判断是否出窗口
④更新结果

进窗口就相当于让right依次向后移动,遇到1无视,遇到0,zero++

当zero > K进行出窗口操作,直到合法为止(即zero < K),出窗口即为遇到1无视,遇到0,zero--

更新结果即是在每次判断完,如果合法就更新结果


代码如下:

public:int longestOnes(vector<int>& nums, int k) {int left = 0, right = 0, n = nums.size();int zero = 0, ret = 0;while(right < n){if(nums[right] == 0) zero++;      // 进窗口while(zero > k)                   // 判断是否出窗口if(nums[left++] == 0) zero--; // 出窗口ret = max(ret,right-left+1);      // 更新结果right++;                          // 下一个元素进窗口}return ret;}
};

题目四:将 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 。

刚开始看这个题可能觉得比较难,因为是可能从数组的两边分别找几个数,最终使得相加等于x,并且还需要相加的数的个数最少,那么我们其实也可以换一种思路,正难则反,两边找最短比较困难,那就中间找最长

找两边比较难,不连续,且没有关联性,那能不能从中间找一段连续的区间,使得中间这一段连续的区间等于数组的总和sum - x,这样就间接的表示找到了所需的元素,接着从所找到的所有集合中,找到个数最多的情况,即为符合条件的情况

因为中间的个数越多,两边的个数就越少,也就是找到了最小操作数

所以题目转化为:找出最长的子数组的长度len,所有元素的和正好等于sum- x,设为target,此时所求的最小操作数就是数组长度 n - len


转化后的这道题和我们所做的题目一非常相似,利用单调性,使用“ 同向双指针 "来优化

同样是定义left和right最开始指向0,每次right进窗口,都加上right所指向的值,直到所加的值 >= target后,执行left++

这里优化暴力方法,right不需要重新回到left的地方,因此right停止的这个地方刚好是从left到right相加 >= target的地方,而此时left向右移动了,那么相加的更小了,所以right如果回到left所指向的位置,也一定会回到这个位置,因此,当left向右移动后,right只需要判断是否向后移动即可,不需要回到前面

所以滑动窗口的步骤:

①left=0,right=0
②进窗口,childsum += nums[right]
③判断是否出窗口,判断childsum > target,如果满足就出窗口,即childsum -= nums[left]
④合适的地方更新结果,即当childsum == target时更新结果

这里的时间复杂度是O(N)的


代码如下:

class Solution {
public:int minOperations(vector<int>& nums, int x) {int n = nums.size(),left = 0,right = 0;int len = -1,sum = 0, childsum = 0;for(auto& iter : nums) sum += iter;//计算数组总和if(sum < x) return -1; // 边界情况int target = sum - x;while(right < n){childsum += nums[right];//进窗口while(childsum > target)//判断childsum -= nums[left++];//出窗口if(childsum == target)//更新结果len = max(len, right-left+1);right++;}if(len == -1) return len;return n - len;}
};

题目五:水果成篮

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

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

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

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

提示:

  • 1 <= fruits.length <= 105
  • 0 <= fruits[i] < fruits.length

示例 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] 这五棵树。

这个题目刚开始读,可能会觉得比较绕,其实读完结合示例是不难理解的,也就是题目可以转化为:

在数组中找出一个连续的子数组的长度,使得这个连续的子数组不超过两种类型的水果,不同的数字表示不同的类型

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

哈希表用于统计暴力枚举中水果出现了多少种,也就是就是找到所有符合条件的子数组,找出最长的那一个子数组即可

解法二:滑动窗口 + 哈希表

在暴力枚举中,遍历每个数组时,每次left++,right都会回到left的位置,重新向后++,但是我们可以思考一下,这里的left++,水果的种类只会有两种情况
①种类减少,right可以++
②种类不变,right不能动

所以既然left++后只有这两种情况,right要么++要么不动,那么right就可以优化为不需要回到left处,只需要判断是否需要++即可

所以滑动窗口的方式解决:

①left=0,right=0
②进窗口
③判断是否出窗口
④更新结果

需要说明的一点:我们这里使用数组hash来模拟哈希表,也可以使用unordered_map<int ,int>来实现哈希操作,其中第一个int表示种类,第二个int表示数量,但是使用unordered_map会频繁的在哈希表中插入删除元素,比较耗时,所以可以考虑使用数组模拟这个哈希过程:

class Solution {
public:int totalFruit(vector<int>& fruits) {int left = 0,right = 0,n = fruits.size();int hash[100001] = {0};int kinds = 0,len = 0;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++;}len = max(len,right-left+1);//更新结果right++;}return len;}
};

题目六:找到字符串中所有字母异位词

给定两个字符串 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" 的异位词。

关于异位词,比如"abc",它的异位词就是a、b、c这三个字母不同顺序排列出来的都称为"abc"的异位词,所以"abc"的异位词就是abc、acb、bac、bca、cab、cba

那么我们通过观察,只要这三个字母每一个字母出现的次数,和原字符串的的每一个字母出现的次数相等,也就是abc都各出现了1次,就说明是该字符串的异位词

所以这里如何记录出现的次数,使用哈希表就可以做到

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

将字符串中所有与p字符串长度相同的子串都找到,每次都与p字符串对应的哈希表作对比,直到比较完所有的字符串为止:

假设p字符串是abc,所给字符串是cbaeba,暴力方法也就是下图所示,依次将每一个红线所画的子串都映射入哈希表中,比较是否与p字符串的映射的哈希表相同


但是仔细观察上述暴力解法,没有必要每次重新哈希映射, 因为第一个子串cab和第二个子串bae,区别就是将c移出哈希表,而将e移进来,所以并不需要第二个子串映射时,将bae重新映射一遍,只需移出一个移进来一个字符即可

解法二:滑动窗口 + 哈希表

这道题与前面所做的题不同,这道题的滑动窗口大小是固定的

滑动窗口的第一种思路:left和right每次同时移动,每次删除一个元素,增加一个元素接口,并且在每次移动前,比较两个哈希表是否相同,相同就表示符合题意,将left下标push_back到vector中即可

过程依旧是:

①left=0,right=0
②进窗口
③判断是否出窗口
④更新结果

代码如下:

class Solution {
public:vector<int> findAnagrams(string s, string p) {int ns = s.size(),np = p.size();int hashs[26] = {0}; //统计字符串 s 中每个字符出现的次数int hashp[26] = {0}; //统计字符串 p 中每个字符出现的次数vector<int> v;bool flag = true;for(int i = 0; i < np; ++i) hashp[p[i]-'a']++;for(int left = 0,right = 0; right < ns; right++){hashs[s[right]-'a']++; //进窗口for(int j = 0; j < 26; ++j){//如果不同就改为falseif(hashs[j] != hashp[j]) flag = false;}if(flag) v.push_back(left);//没返回false表示相同,所以更新结果//在s中子串等于np后,每次left++right++if(right >= np-1) hashs[s[left++]-'a']--;//出窗口flag = true;}return v;}
};

滑动窗口的第二种思路:不需要比较两个哈希表是否相同,只需要引入一个变量count,表示有效字符的长度

因为虽然将哈希表用一个大小为26的数组表示后,每次比较只需要比较26次,但是还是效率不高,所以这里引入一个变量count,表示有效字符的长度,p字符串映射到hashp数组中,字符串s遍历时映射到hashs数组中,p字符串大小为np

如果hashs[right]++后,如果hashs[right] <= hashp[right],那就count++,表示是有效字符
如果hashs[right]++后,hashs[right] > hashp[right],count就不变,说明不是有效字符

 假设已经走到这里了

此时count等于np时,表示当前的字符串中包含了p字符串的异位词,所以此时仅需判断长度是否与np相同,相同就说明符合题意,上述例子就不同,因为np是3,而当前字符串是4

如果不同,就需要hashs[left]--,在--之前, 进行判断hashs[left] > hashp[left],如果大于count就不需要变,如果小于就需要count--,left++,直到长度相同为止,将left尾插到vector中

所以此时left需要++,还有hashs[left]--,--前发现hashs[left]是2,大于hashp中的1,所以此时hashs[left]--,不影响有效长度,--完成后,如下:

此时count == np,且子串长度也等于np,就可以将left尾插到vector中了,接着向后执行即可,直到right超过界限

所以步骤是:

①left=0,right=0
②进窗口,若hashs[right] <= hashp[right] -> count++
③判断是否出窗口,hashs[left] <= hashp[left] -> count--,否则count就不变
④更新结果,count == np时,更新

代码如下:

class Solution {
public:vector<int> findAnagrams(string s, string p) {int ns = s.size(),np = p.size(), count = 0;int hashs[26] = {0}; //统计字符串 s 中每个字符出现的次数int hashp[26] = {0}; //统计字符串 p 中每个字符出现的次数vector<int> v;for(int i = 0; i < np; ++i) hashp[p[i]-'a']++;for(int left = 0,right = 0; right < ns; right++){   //进窗口+维护countif(++hashs[s[right]-'a'] <= hashp[s[right]-'a']) count++;if(right - left + 1 > np) //判断{if(hashs[s[left]-'a'] <= hashp[s[left]-'a'])//出窗口count--;hashs[s[left]-'a']--;left++;}if(count == np) v.push_back(left);//更新结果}return v;}
};

题目七:串联所有单词的子串

给定一个字符串 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"] 顺序排列的连接。

这道题初一看,比较难,但是我们转换一下思路,将words中的字符串看成一个字符,就相当于:

题目就变为了在"bacabd"字符串中找"ab",其中ab可以是不同的顺序,那么此时这个题目就和上一道题几乎一模一样了,只是此题中的ab是字符串而已

所以解法一:滑动窗口 + 哈希

下面着重说说和上一题的不同之处:

不同一:哈希表不同

上一道题的哈希表是用一个数组表示的,但是此题是一个字符串,所以采用unordered_map<string, int>来映射

不同二:left、right指针的移动不同

因为上一题是一个一个字符移动的,而此题是将固定长度的字符串当做一个字符,所以这里left和right移动时,就需要移动固定的长度,如果按上面画图的例子看,就需要移动三步,因为words中的字符串长度是3

所以left和right移动的步长是一个单词的长度len

不同三:滑动窗口执行的次数不同

如上图所示,有可能是红色线标注出来的对应的words中的字符串,也有可能是紫色或是绿色线标注出来的对应,所以这里的滑动窗口执行的次数是len次

之所以是len次,是因为第len+1次开始就与之前的重复了

下面的代码截取string中的一部分,使用的是给出起始位置,给出需要的数量,也就是
string tmp(s,right,len)这样使用的,其中s指从string s中获取,right和len是指从right所指向的位置开始截取len个

当然那也可以使用substr,例如s.substr(right,len),这两种方式的效果是一样的

代码如下:

class Solution {
public:vector<int> findSubstring(string s, vector<string>& words) {int len = words[0].size(), ns = s.size(), count = 0, wordlen = words.size();unordered_map<string, int> mp1;// 保存 words 中所有单词的频次unordered_map<string, int> mp2;vector<int> v;for(auto& iter : words) mp1[iter]++;for(int i = 0; i < len; i++)// 执行 len 次活动窗口{for(int left = i,right = i; right + len <= ns; right+=len){string tmp(s,right,len); //tmp 类似于的单个字符的right指向的值mp2[tmp]++;if(mp1.count(tmp) && mp2[tmp] <= mp1[tmp]) count++; // 进窗口 + 维护countif(right-left+1 > wordlen*len) // 判断{// 出窗口 + 维护countstring cur(s,left,len);//cur 类似于的单个字符的left指向的值left += len;if(mp1.count(cur) && mp2[cur] <= mp1[cur]) count--;mp2[cur]--;}if(count == wordlen) v.push_back(left);// 更新结果tmp.clear();}mp2.clear(); // 每次执行完 mps 置空count = 0;   // 每次执行完 count 置空}return v;}
};

上面有个优化的点:在进窗口时先执行mp1.count(tmp),是为了确保mp1中是有这个tmp单词的,如果mp1中没有这个单词,就不需要比较出现的次数了,提高效率

如果不加这个语句,每次比较时即使mp1中没有出现tmp单词,mp1哈希表依旧会将tmp映射进入,这会造成一定的时间消耗


题目八:最小覆盖子串

给你一个字符串 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 的子串中,
因此没有符合条件的子字符串,返回空字符串。

解法一:暴力解法 + 哈希

设计两个哈希数组,将t中的字符哈希进数组1,然后依次遍历字符串s,每遍历一个字符映射进数组2,直到数组2中对应的字符大于等于数组1中对应字符的 个数,此时就满足条件

从字符串s中的第一个字符开始,依次向后遍历,直到找到包含字符串t中的元素或是超过s长度为止,找到所有符合要求的,找出最小的长度的子串

解法二:滑动窗口 + 哈希 + count计数

假设从left开始,直到right找到了符合条件的字符串,此时记录这个字符串的长度以后,再left++,此时会有两种情况:

①left++后符合要求,所以right不动,此时可以继续更新结果
②left++后不符合要求,此时right就需要右移


和上面异位词的一样,可以设一个count值,用于取代每次遍历两个hash数组,提高效率

这里的count与异位词的count代表的含义不同,这里的count代表的是hashs数组中的有效种类,不是个数,例如目标子串是"abb"此时就只需要统计有效种类是两种,在进窗口后,如果该字符对应的个数和目标子串的个数一样,那就表示是有效字符,此时count++

当出窗口时,如果此时的字符对应的个数和目标子串的个数一样,就需要count--

在出完窗口,如果还是满足条件的,就继续更新结果

①left=0,right=0,count = 0
②进窗口,若++hasht[in] == hasht[in] -> count++
④判断是否更新结果,count == kinds时,更新结果
③判断是否出窗口,hashs[out] == hasht[out] -> count--,否则count就不变,继续循环更新结果


代码如下:

class Solution {
public:string minWindow(string s, string t) {int ns = s.size(), len = INT_MAX, kinds = 0, begin = -1;int hashs[128] = {0};int hasht[128] = {0};for (auto& it : t) if (hasht[it]++ == 0) kinds++;for (int left = 0, right = 0, count = 0; right < ns; right++) {char in = s[right]; // 进窗口的字符inif (++hashs[in] == hasht[in]) count++; // 进窗口 + 维护countwhile (count == kinds) {if (right - left + 1 < len) // 更新结果{len = right - left + 1;begin = left;}char out = s[left++];// 出窗口的字符outif (hashs[out]-- == hasht[out]) count--; // 出窗口 + 维护count}}if (begin == -1) return ""; //说明没有符合条件的子串return s.substr(begin, len);}
};

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

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

相关文章

Java modbus 实现RTU串口作为slave(服务端)读写数据

这里要了解下modbus的RTU和TCP 的几个名称关系&#xff1a; Modbus/RTU&#xff1a;主站 和从站 关系 Modbus/TCP&#xff1a;客户端和服务端关系 关系 主站主动找从站读写数据 客户端主动找服务端读写数据 所以当使用Modbus/TCP时&#xff0c;主站一般作为客户端&#xff…

树莓派发送指令控制FPGA板子上的流水灯程序

文章目录 前言一、树莓派简介二、整体实现步骤三、树莓派设置四、树莓派串口代码五、Verilog代码5.1 串口接收模块5.2 流水灯模块 六、quartus引脚绑定七、 运行效果总结参考 前言 ​ 本次实验的目的是通过树莓派和FPGA之间的串口通信&#xff0c;控制FPGA开发板上的小灯。实验…

Excel常用操作

计算支付成功率 使用公式 ROUND(B2/C2,4)*100&"%" 字符串拼接 将A1-A10的数字用英文逗号拼接 TEXTJOIN(",",TRUE,A1:A10) 将A1-A10中大于5的数字用英文逗号拼接 ARRAYFORMULA(TEXTJOIN(",",TRUE,IF(A1:A10>5,A1:A10,"")…

未来想从事营销策划类的工作,需要怎么学习?

从事营销策划类的工作&#xff0c;提升和学习主要从以下三个方面&#xff1a; 一、营销底层逻辑的搭建 二、营销系统知识的构建 三、大量营销案例的积累 营销入门&#xff0c;其实大多数人一直都在入门的道路上&#xff0c;每个人都是终身学习者。虽然从事营销工作十年多了…

2024年5月中,AITOP100平台活动专区迎来六场AI大赛盛事!

AITOP100平台的活动专区在2024年5月中旬更新的6场AI大赛来了&#xff01; 随着人工智能技术的飞速发展&#xff0c;AI设计已经成为了创新与创意的新领域。2024年5月中旬&#xff0c;由腾讯研究院、剪映、站酷等互联网大厂主办的6场AI设计大赛震撼来袭&#xff0c;为广大AI设计…

【数据分析面试】43.寻找给小费最多的客人(Python:字典用法)

题目&#xff1a; 寻找给小费最多的客人 &#xff08;Python) 给定两个非空列表user_ids和tips&#xff0c;编写一个名为most_tips的函数&#xff0c;用于找到给小费最多的客户。 示例&#xff1a; 输入&#xff1a; user_ids [103, 105, 105, 107, 106, 103, 102, 108, 1…

短剧看剧系统,当前互联网热门项目工具系统模板。

目录 揭秘爆款神器&#xff1a;短剧看剧系统&#xff0c;让你的内容火遍全网&#xff01; 一、短剧看剧系统&#xff1a;一站式解决方案 二、灵活定价&#xff0c;实现收益最大化 三、高效管理&#xff0c;团队协作更轻松 四、数据驱动&#xff0c;精准把握市场动态 五、智…

设置linux终端用户输入空闲一段时间后就自动断开(linux终端超时自动断开)

在 /etc/profile 中加入TMOUT变量即可。 在文件的最后追加以下两行 export TMOUT600 # 600秒内无操作就断开。 readonly TMOUT # 将变量设置为只读&#xff0c;防止用户更改如图

企业计算机服务器中了rmallox勒索病毒怎么解密,rmallox勒索病毒解密工具流程

在当今数字化时代&#xff0c;越来越多的企业依赖计算机服务器进行办公开展业务&#xff0c;计算机服务器犹如企业的心脏&#xff0c;能够为企业存储许多重要的核心信息&#xff0c;帮助企业有效的开展各项工作业务&#xff0c;提高企业的生产效果&#xff0c;但网络是一把双刃…

springMVC基础使用(示例)

maven依赖&#xff08;javax.servlet-api版本与spring-webmvc班恩要匹配不然会报java.lang.NoSuchMethodError: javax.servlet.http.HttpServletRespons&#xff09;&#xff1a; <dependencies><dependency><groupId>javax.servlet</groupId><arti…

CAN模块开发问题概述

问题一 问题描述 工作环境&#xff1a;ECU外接canoe 操作&#xff1a;使用CANoe模拟发送NM报文&#xff0c;然后停发或者断开CANoe 现象&#xff1a;程序跑死&#xff0c;调用call stack查看压栈情况如下图所示 定位代码如下图所示。可见是由于CAN模块在设置Controller状态时…

计算机毕业设计 | vue+springboot调查问卷管理系统(附源码)

1&#xff0c;研究目的 在进入21世纪以后&#xff0c;互联网得到了蓬勃的发展&#xff0c;电子问卷调查也开始逐渐流行起来。传统纸质问卷和电子问卷相比较后&#xff0c;传统问卷还存在很多弊端&#xff1a; 问卷分发起来比较困难&#xff0c;并且分发试卷耗费大量的金钱和时…

网络安全法中关于网络信息的保护和监管,有哪些规定?

网络安全法作为我们数字时代的重要法律保障&#xff0c;对于网络信息的保护和监管有着明确且详细的规定。这些规定不仅体现了国家对于网络安全的重视&#xff0c;也为我们每个人在数字世界中提供了坚实的法律屏障。 首先&#xff0c;我们来看一个关于网络运营者主体责任的案例。…

uniapp开发安卓app使用文字转语音技术

在 UniApp 开发安卓应用时&#xff0c;要实现文字转语音&#xff08;Text-to-Speech, TTS&#xff09;技术&#xff0c;你可以利用 UniApp 的跨平台能力结合原生模块或第三方服务来实现。以下是一些建议的步骤和方法&#xff1a; 1. 使用 UniApp 原生模块&#xff08;如果支持…

【架构-17】通信系统架构设计理论

通信系统网络架构 1. 局域网网络架构 拓扑结构&#xff1a;星型、总线型、环型、树型。 网络架构&#xff1a;单核心架构&#xff08;结构简单&#xff0c;地理范围受限&#xff09;、双核心架构&#xff08;网络拓扑结构可靠&#xff0c;投资较单核高&#xff09;、环型架构…

更高效的数据交互实现丨 DolphinDB Arrow 插件使用教程

Apache Arrow 是一种跨语言的内存数据交换格式&#xff0c;旨在为用户提供高效的数据结构&#xff0c;以实现在不同的数据处理系统之间共享数据而无需进行复制。它由 Apache 软件基金会开发和维护&#xff0c;目前已经成为许多大型数据处理和分析框架的核心组件之一。在分布式框…

收藏:如何轻松建立CRM系统的帮助中心

大家好&#xff0c;今天咱们来聊聊怎么给公司的CRM系统建个帮助中心。为什么CRM系统需要建立帮助中心呢&#xff1f;很简单&#xff0c;就是为了让客户、员工在遇到问题时能有个快速找到答案的地方&#xff0c;提升咱们的服务质量和内部工作效率。 一、为什么需要建立CRM系统的…

【SQL每日一练】获取PADS公司用户名称和各职业总数并根据格式输出

文章目录 题目一、解析二、题解1.MySQL 题目 生成以下两个结果集&#xff1a; 1、查询 OCCUPATIONS 表中所有名字&#xff0c;紧跟每个职业的第一个字母作为括号&#xff08;即&#xff1a;括在括号中&#xff09;&#xff0c;并按名字顺序排序。例如&#xff1a;AnActorName…

使用 Python 进行图像验证码识别训练及调用

目录 1、验证码识别原理1.1 Tensorflow 介绍1.2 Tensorflow 运行原理1.3 卷积神经网络 CNN&#xff08;Convolutional Neural Networks&#xff09; 2、验证码识别实现步骤2.1 安装第三方模块2.1.1 安装 TensorFlow 模块2.2.2 安装 cuda2.2.3 下载 cudnn 2.2 读取验证码样本形成…

ShellCode详解三

直接进入正题。 在完成正式的shellcode代码导出之前&#xff0c;我们先手动的对代码进行导出&#xff0c;以使各位同学更加了解其原理。 手动注入shellcode 1、我们利用DLE工具找到上一节中我们生成的PE文件的代码段位置 上述图片就是我们的代码段位置 2、利用WinHex工具我…