定长滑动窗口-----金丹篇

今天是不同的类型,话说定长滑动窗口中,经常会出现这样一类题目,给你两个串,一个串的排列可能是另一个串的子串,让你求出子串开始的位置,或者证明是否存在这样的子串。

一.热身阶段

 力扣567.字符串的排列

https://leetcode.cn/problems/permutation-in-string/description/https://leetcode.cn/problems/permutation-in-string/description/

题目:

给你两个字符串 s1 和 s2 ,写一个函数来判断 s2 是否包含 s1 的 排列。如果是,返回 true ;否则,返回 false 。

换句话说,s1 的排列之一是 s2 的 子串 。(s1和s2只包含小写字母)

示例:

输入:s1 = "ab" s2 = "eidbaooo"
输出:true
解释:s2 包含 s1 的排列之一 ("ba").

这时候就有兄弟会问了,这咋用模板呢,s1的排列这么多个,我难道要一个一个列出来吗,主播有什么简单的方法吗?有的兄弟有的,我们可以化整为零嘛,s1字符串说到底是由k(s1的大小)个字符组成的,那么我们就可以用一个数组维护s1的字符以及对应的数量,这一样一来我们就将问题转换为窗口中字符以及对应的数量是否和s1相同即可。细节上,我们将s1存在的字符在数组中+1,入窗时,将字符在数组中-1,最终看数组中的元素是否全为0。

class Solution {
public:
    bool checkInclusion(string s1, string s2) {
       int k=s1.size();
       vectorres(26,0);//记录s1的字符及其对应的数量
       for(char c:s1){//增强for写法
          res[c-'a']++;
       }
       int index=0;
       for(int i=0;i

另一个类型求窗口的起始位置。

力扣 438.找到字符串中所有字母异位词

https://leetcode.cn/problems/find-all-anagrams-in-a-string/description/https://leetcode.cn/problems/find-all-anagrams-in-a-string/description/

题目:给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。(s和p只包含小写字母)

示例:

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

思路和上一题一致,兄弟们可以练练手,有什么问题可以在评论区讨论一下。

二.练功阶段

接下来这题是438的进阶题。

力扣30.串联所有单词的子串

https://leetcode.cn/problems/substring-with-concatenation-of-all-words/description/https://leetcode.cn/problems/substring-with-concatenation-of-all-words/description/

题目:

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

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

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

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

示例

输入: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] 也是可以的。

根据上一题的经验,熟练掌握模板的兄弟一下就想到,这不就是把字符串s1变成了一个字符串数组吗?直接用一个unordered_map维护字符串数组中的字符串,再用一个unordered_map维护窗口中的字符串,然后再比较两个是否相同不就好了?但是在移动窗口的时候是移动一个字符长度还是移动一个字符串呢?根据上一题的经验,应该移动一个字符,但是移动一个字符显然上一个用来维护窗口的map是不能再次使用的,意味着我们需要重新维护现在窗口的字符串,这肯定是违背了滑动窗口的意义。那如果我们移动一个字符串长度,假设s的长度是len_s,words的大小是n,每个字符串的长度是m,窗口的开始位置是0,移动一个字符串长度,即窗口的下一个开始位置是0+m,这样会产生一个问题就是1,2,3........m-2,m-1是直接被跳过的,显然这也是错误的。如何解决呢?我们重新看一下题目,为什么题目特别强调每个字符串的长度都相同呢?这显然是方便了窗口在滑动时,单词的进入和退出时,我们对窗口中的单词的维护,所以,每次滑动一个单词长度应当是合理的,我们需要解决的是,起点在1,2,3........m-2,m-1时的情况,所以我们可以用m个窗口滑动。现在整理一下大体的思路,我们需要m(字符串数组中单词的长度)个窗口分别从m个起点开始滑动(可以画图理解下),每个窗口每次移动m的字长,这样我们就不会有所遗漏了。细节上,用一个unordered_map维护单词数组中的单词,一个vector>数组维护每一个窗口中的单词。

class Solution {
public:
    vector findSubstring(string s, vector& words) {
       int n=words.size(),m=words[0].size();
       vectorans;//记录答案
       unordered_mapmp;//维护words中的单词数
       for(string w:words){
         mp[w]++;
       }
       vector>sp(m);//需要m个滑动窗口
       for(int i=0;i

兄弟们可以抽象理解为一条总路线上,m辆高铁从0........m-1起点开始出发,每辆高铁的长度是n*m,速度是m/s,单词数组抽象成n个长度相同的高铁站,它可以全排列在路线上,一旦有高铁路线上的所有高铁站和单词数组中的高铁站一样,就在答案中添加这个高铁的起始位置。兄弟们可以好好回味一下。

三.梳理关节

遇到这种题,需要维护能变换的和窗口中的,其他和模板大差不差。好好休息,明天是母亲节。

你可能感兴趣的:(定长滑动窗口,算法,c++,leetcode)