首页 > 其他分享 >滑动窗口题目解析

滑动窗口题目解析

时间:2023-08-29 13:31:53浏览次数:41  
标签:right 题目 string int 哈希 滑动 解析 size

最近刷了两道非常经典的滑动窗口题目。感觉对自己帮助非常大,所以写下这篇博客来详细解释一下这两道题目,同时验证自己是否完全理解这两道题目。

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

题目链接:https://leetcode.cn/problems/find-all-anagrams-in-a-string/

滑动窗口题目解析_滑动窗口

这道题目的要求很简单,即要求我们在s串中找到一个字串,这个子串具有一个特点那就是这个子串中的所有字母都是由p中的字母组成的。即子串中的字母数量和种类和p是一样的。

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

解法一的思路很简单我们可以枚举出s的所有字串然后从字串中寻找符合条件的字串然后,将值填入。

滑动窗口题目解析_滑动窗口_02

我们接下来来优化一下这个思路,首先思考一下当right移动到之后还有必要返回到left位置开始重新枚举吗?

以一个普通的字符串为例子

滑动窗口题目解析_字符串_03

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

class Solution {
public:
    bool issame(int* hash, string& p, int* hash2)
    {
        for (int i = 0; i < p.size(); i++)
        {
            if (hash[p[i] - 'a'] != hash2[p[i] - 'a'])
            {
                return false;
            }
        }
        return true;
    }//这里使用了一个函数遍历完一遍哈希表确定两个哈希表是否相等
    vector<int> findAnagrams(string s, string p) {
        vector<int> ret;
        int hash[26] = { 0 };//作为s的哈希表
        int hash2[26] = { 0 };//作为p的哈希表
        if (p.size() > s.size())
        {
            return ret;
        }
        int left = 0;
        int right = 0;
        for (int i = 0; i < p.size(); i++)
        {
            hash2[p[i] - 'a']++;
        }
        //进窗口
        while (right < p.size())
        {
            hash[s[right] - 'a']++;
            right++;
        }
        while (right <= s.size())//这里不能将等号删除,否则会少记录一种情况,例如abab 和ab
            //当right将b放到哈希表中后right++等于了size(),不会进入循环记录最后一个答案
            //所以需要将等号加上
        {
            //记录答案
            if (issame(hash, p, hash2))
            {
                ret.push_back(left);
            }
            // 再次进窗口
            if (right < s.size()) {//这里需要判断一下right是否越界了,否则在right为s.size()的时候还是会进入hash表导致错误
                hash[s[right] - 'a']++;
            }
            //出窗口
            hash[s[left++] - 'a']--;
            right++;
        }
        return ret;
    }
};

这个代码还可以继续去优化,优化的点就在于于哈希表的比较,在上面的代码中我们是遍历完两个哈希表来比较哈希表是否相等的,这样就导致了每一次的检查都要遍历一遍哈希表,导致代码效率变低。

为了解决这个问题,可以使用一个count来计算符合条件的字符数量有好多。当count等于了p.size(),就可以更新答案。

下面是图解

滑动窗口题目解析_子串_04

滑动窗口题目解析_子串_05

下面是代码实现:

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        vector<int> ret;//返回的答案
        //可以进行优化,使用count来统计哈希表中的有效字符的个数
        int hash1[26] = { 0 };//记录p中字母出现的次数
        int hash2[26] = { 0 };//记录滑动窗口中字母出现的次数
        if (p.size() > s.size())
        {
            return ret;
        }//特殊情况
        for (int i = 0; i < p.size(); i++)
        {
            hash1[p[i] - 'a']++;
        }
        int left = 0;
        int right = 0;
        int count = 0;//记录当前窗口中有效字母的个数
        while (right < s.size())
        {
            //进窗口
            hash2[s[right] - 'a']++;
            if (hash2[s[right] - 'a'] <= hash1[s[right] - 'a'])//如果此时进入的这个字符的个数是小于哈希表1中
                //这个字母出现的次数的,代表这是一个有效字母所以让count++
            {
                count++;
            }
            //判断出窗口
            while (right - left + 1 > p.size())
            {
                if (hash2[s[left] - 'a'] <= hash1[s[left] - 'a'])
                {
                    count--;
                }//这里代表出的是一个有效的元素
                hash2[s[left++] - 'a']--;
            }
            if (count == p.size())
            {
                ret.push_back(left);
            }
            right++;
        }
        return ret;
    }
};

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

题目链接:30. 串联所有单词的子串 - 力扣(LeetCode)

滑动窗口题目解析_滑动窗口_06

这道题目有一个很重要的条件那就是words中每一个string的字符数是相等的。由此就能够知道出窗口的条件。

首先我们来看题目的求,在示例一中,在s字符串中bar foo这一段子串恰好完全就是由words中的两个字符串构成的,所以在s中的barfoo就是一个答案,将b的下标放到答案vector中。再然后便是在s字符串中的foobar这一段也是由words中的foo bar构成的所以最后将f的下标放到答案vector中。需要注意的是如果s字符串中出现了baroof这样的字符串是不符合答案要求的,因为oof并不是words数组中的string

下面是解法:

解法:

这道题目的暴力解法和上面那道题可以说是一摸一样的,因为如果将foo,看作字母a,bar看作字母b。然后将s中的字符每三个一组当作一个字符,那么这道题目就变得和上面一摸一样了。那么这道题也就可以使用滑动窗口和count优化了

代码:

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        //这道题目的思路为使用滑动窗口,首先将words中的每一个字符串的长度记录下来,然后在s中使用滑动窗口,来寻找满足条件的子串
        //将words中的字符串当作一个字符那么这道题目的思路也就是第438道题的思路
        unordered_map<string,int> hash1;//储存words中的字符串出现次数
        vector<int> ret;//用于储存答案
        int len = words[0].size();//得到单个字符串的长度
        int m = words.size();//有效字符串应该达到的次数
        int sum_len = len*m;
        for(int i = 0;i<m;i++)
        {
            hash1[words[i]]++;
        }
        for(int i = 0;i<len;i++)//滑动窗口进行的次数
        {
            int left = i;
            int right = i;//创建left和right指针
            unordered_map<string,int>hash2;//统计滑动窗口中的字符串出现次数
            int count = 0;
            while(right+len<=s.size())//开始滑动窗口
            {
                //进窗口
                string intmp = s.substr(right,len);//将字符串s从right位置开始的len个位置的字符拿出来
                hash2[intmp]++;
                if(hash1[intmp]&&hash2[intmp]<=hash1[intmp])
                //这里进行了一次优化,因为如果intmp在hash1中没有出现那么就没有必要去比较
                //并且对于map而言如果这个元素在之前没有出现过,而这里选要它出现的次数则会将这个字符串先放到map中去
                {
                    count++;
                }//当hash中存在这个字符串,并且hash2中这个字符串出现的次数小于等于hash1中出现的次数那就让count++
                //代表一个有效的字符串出现了
                //判断
                while(right -left+1>sum_len)
                {
                    string outtmp = s.substr(left,len);
                    if(hash1[outtmp]&&hash2[outtmp]<=hash1[outtmp])
                    {
                        count--;
                    }//代表一个有效的字符串被移除了
                    hash2[outtmp]--;
                    left+=len;
                }
                //更新答案
                if(m == count)
                {
                    ret.push_back(left);
                }
                right+=len;
            }            
        }
        return ret;
    }
};

题目三:最小覆盖子串

题目链接:LCR 017. 最小覆盖子串 - 力扣(LeetCode)

滑动窗口题目解析_滑动窗口_07

首先来分析题目的要求结合示例题目

这道题目的暴力解法:暴力枚举+哈希表。将每一种枚举出来的子串放到哈希表中去,然后和t比较如果所有有效字母的数量是相等或大于的代表这是一个符合条件的子串,然后记录长度。下面我们来从暴力枚举的思路上检查是否存在可以优化的地方

滑动窗口题目解析_子串_08

然后还有一个优化,也就是关于哈希表比较的优化这里我们为了避免每一次比较都要遍历完整一遍的哈希表,这里就还是采用一个count来记录有效字母的种类,需要注意这里不是有效字母的数量,而是种类。因为题目的要求是t中的每一种字母都出现在了子串中才是符合条件的子串,如果t中每一个字母多次出现在了s中count是不能++的。不然就会出现错误。

故在滑动窗口中某一个有效字符出现的次数等于了在t中出现的次数时,代表这是一种有效的字符被增加到了窗口中count可以++,而在出窗口的时候,假设这个要出窗口的元素在窗口中出现的次数等于在t中出现的次数,那么删除这个元素代表的就是一个有效元素的删除,所以让count--。


class Solution {
public:
    string minWindow(string s, string t) {
        if (t.size() > s.size())
        {
            return "";
        }
        unordered_map<char, int>hash1;//统计t中字符出现的次数
        unordered_map<char, int>hash2;//统计滑动窗口中字符出现的次数
        int count = 0;//统计有效字符出现的种类
        int left = 0;
        string ret = s;
        int flag = 0;//默认为没有修改
        int right = 0;
        for (int i = 0; i < t.size(); i++)
        {
            hash1[t[i]]++;
        }
        int len = hash1.size();
        while (right < s.size())
        {
            //进窗口
            hash2[s[right]]++;
            if (hash1[s[right]] && hash2[s[right]] == hash1[s[right]])
            {
                count++;//当这个字符出现的次数在进入到窗口中后,
                //窗口中出现的字符串刚好等于haxi1中这个字符出现的次数
                //代表这是一个有效的字符种类
            }
            while (count == len)
            {
                string tmp(s.begin() + left, s.begin() + right + 1);
                if (tmp.size() <= ret.size())
                {
                    ret = tmp;
                    flag = 1;
                }
                if (hash1[s[left]] && hash1[s[left]] == hash2[s[left]])
                {
                    count--;
                }//当我们在没有删除left对应的这个字符的时候,这个字符的出现次数刚好等于这个字符在hash1中出现的次数
                //那么在删除完left之后代表的便是有效的字符种类少了一种
                hash2[s[left]]--;
                left++;
            }
            right++;
        }
        if (flag == 0)
        {
            return "";
        }
        else
        {
            return ret;
        }
    }
};

这些滑动窗口题目的代码其实并不是很难写,重要的是我们要知道什么时候使用滑动窗口,即滑动窗口的思想。因为我的水平优先,所以如果阅读的时候发现了错误,欢迎指出。

标签:right,题目,string,int,哈希,滑动,解析,size
From: https://blog.51cto.com/u_15838996/7275782

相关文章

  • linux(ubuntu)能ping ip,不能ping域名。无法解析域名DNS指向127.0.0.53问题处理
    故障现象:无法上网。ping互联网ip地址能通信,ping域名无法解析。用nslookupwww.qq.com返回127.0.0.53无法解析的问题。重启无法解决。编辑/etc/resolved.conf配置文件dns写的127.0.0.53.直接添加新的dns,果reboot重启之后,还是原来的内容不变首先修改/etc/systemd/resolved.conf文件......
  • FPGA芯片结构介绍及工作原理解析
     FPGA工作原理与简介  如前所述,FPGA是在PAL、GAL、EPLD、CPLD等可编程器件的基础上进一步发展的产物。它是作为ASIC领域中的一种半定制电路而出现的,即解决了定制电路的不足,又克服了原有可编程器件门电路有限的缺点。  由于FPGA需要被反复烧写,它实现组合逻辑的基本结构不......
  • 深入解析:树结构及其应用
    文章目录学习树的基本概念理解树的遍历方式学习堆和优先队列的应用案例分析:使用堆进行TopK元素的查找结论......
  • 数位DP详细解析
    1.定义与原理2.例题一:题目Acwing1081.度的数量思路我们做数位\(DP\)时,一般有如下两个技巧方便做题,理清思路:1.对于求一段数中满足条件的数的个数,可以用前缀和的方法完成,即$ans=dp(r)-dp(l-1)$;2.在想思路时,可以把问题转换成树的形式,对每个步骤分情况讨论,下面拿......
  • Learn Git in 30 days——第 07 天:解析 Git 资料结构 - 索引结构
    写的非常好的一个Git系列文章,强烈推荐原文链接:https://github.com/doggy8088/Learn-Git-in-30-days/tree/master/zh-cn我们知道在Git里两个重要的资料结构,分別是「物件」与「索引」,这篇文章主要用来解说「索引」的细节。使用Git版本控制的过程中,或许你可以很轻易的了解gi......
  • 一类字符串解析题目的思考
    一类字符串解析题目的思考相关题目最近整理发现,某些机考场景比较喜欢对复杂字符串做解析,例如:394.字符串解码1190.反转每对括号间的子串726.原子的数量特征其具体的表现为,给出一个字符串,给出一个基本结构字符串,例如{abc},是一个三明治(肉夹馍)结构,与扁平化json类似......
  • 关于UE GAS GameplayEffect中SetByCaller的解析
    在GAS中,GameplayEffect(简称GE)里面,在涉及到Magnitude的地方,针对MagnitudeCalculationType都会有一个选项“SetByCaller”,其本质,是把Magnitude的具体数值,交由开发者在代码中决定。如果设置为“SetByCaller”,它都需要填写一个DataTag,其本质是,在GameplayEffect实例中,它有一个......
  • 05 地址解析协议ARP
    地址解析协议(ARP)ARP(AddressResolutionProtocol)地址解析协议:根据已知的IP地址解析获得其对应的MAC地址ARP工作流程1.HOST1ARP缓存HOST1通信之前需要封装数据包,其中在封装二层数据链路层时,终端查询自己的ARP缓存表,ARP缓存表维护一个IP和MAC地址的对应关系,根据对端IP地......
  • C语言北邮2023题目[2023-08-28]
    C语言北邮2023题目[2023-08-28]计算机实习李晶杨金翠孙鹏飞李峥参考资料C语言程序设计的教材及相关课堂资料搜索引擎时间表8.28–9.01时间周一周二周三周四周五内容宣讲实践实践实践实践节次1-4节1-5节1-5节1-5节1-5节9.04-9.08时间周一......
  • NOIP2013提高组初赛易错题解析
    7. 正解:可以画出递归树,画出后应该是这样子的 画出递归树,就可以得出答案时间复杂度为O(Fn) 15. 正解:2T(n/2)=O(logn)T(n)=2*T(n/2)+2*n=O(nlogn)三.2. 错误原因:蒙的正解:通过观察,可以找到递推关系式,f[n]=1/n*(n+f[1]+f[2]+...+f[n]),f[1]=0,f[2]=2,经过计算......