当前位置: 首页 > web >正文

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

一:题目

解释:比如abc的异位词就是 bac cab cba这种重排列子串,我们返回所有异位词的起始索引即可

二:算法

首要的问题是我们在s中取出和p等长的字符串后,此时如何判断两个字符串是异位词?

这种判断某个变量的个数,采取哈希表是最高效的,p在一个哈希表内,我们在s中取出的字符串也在一个哈希表内,对比两个哈希表是否相等即可!

而s和p又都仅仅包含小写字母,所以只有26种可能,所以用一个26大小的数组来模拟哈希表,这样a对应0下标,z对应25下标,刚刚好!所以得到任意一个小写字母,只需减去'a'即可变成0~25范围内的下标!

①:暴力

left从左到右以不同元素作为起点,然后right向右遍历,取到和p等长的字符串,该字符串和p字符串都放在哈希表中比较即可,比较完成,哈希表双双情况,然后下一次left++,right再次从left开始位置向后取和p等长字符串再放进哈希表和p所以的哈希表比较,以此类推....

所以时间复杂度为O(N^2)

②:滑动窗口普通版

例子数组:s = "cbaebabacd", p = "abc"

暴力能优化的点:

1:我们不用将哈希表全部清空,比如现在哈希表中是s中的"cba",下次我们只需清除'c',然后填入'e',形成"bae"即可!

2:所以right也不用在回到前面再次向后遍历取和p一样长的字符串了,其只需无脑++

所以其实这个题目是最适合采取滑动窗口的题目,双指针甚至移动的频率和步长都一致!

但是这个滑动窗口+判断两个哈希表是否相等(check函数)的算法,并不优秀,因为所用调用check函数的时间复杂度为 O(n)!因为单次调用check函数是O(26),而最坏情况是调用s字符串的个数n次,所以为O(26n),大O后为O(n)!所以每次都调用check函数是很影响速率的!

③:滑动窗口优秀版

而更优秀版本是更难理解的,其时间复杂度低至O(1),因为其不直接去判断两个哈希表是否相等,而是用一个变量count来表示窗口内的字符串的有效字符的个数,而有效字符指的就是p字符串中的字符,但是这里有个很难理解的的:

例子1:

p字符串:"abc"

窗口字符串:"bbb"

此时有效字符为'a','b','c',但是我们的count不是为3,而是为1!

所以有效字符指的是:

由于p中的a只有一个,所以窗口中的a只会有一个有效

由于p中的b只有一个,所以窗口中的b只会有一个有效

由于p中的c只有一个,所以窗口中的c只会有一个有效

而此时窗口中全部b,但是只有一个b有效,所以count为1!

所以当count==3的时候,此时表示窗口中一定是1个a,1个b,1个c

例子2:

p字符串:"abb"

窗口字符串:"cbb"

此时有效字符为'a','b',但是我们的count不是为1,而是为2!

所以有效字符指的是:

由于p中的c只有一个,所以窗口中的c只会有一个有效

由于p中的b有两个,所以窗口中的b有两个算作有效

而此时窗口中有两个b,所以count为2!

所以当count==3的时候,此时表示窗口中一定是1个a,2个b

总结:count 统计当前窗口中满足「字符频率 ≤ 目标频率」的字符种类数

所以count的理解至关重要,岂不是遇见和p字符串中字符相同的就++,而是有个数限制!

换算为代码思路:

1:进窗口:先判断字符 c 在存储窗口的哈希表中的频率若还未达到存储p哈希表频率,则count++

2:出窗口:若出窗口后,字符 c 在存储窗口的哈希表中的频率<存储p哈希表频率,则count--

三:代码

①:普通版

普通版就是更易理解的版本,其虽然不如优秀版,但是由于其仍采取了滑动窗口的思路,所以仍以通过代码测试:

class Solution {
public:// 检测两个哈希表是否相等bool check(int* hash1, int* hash2) {//逐元素检测 for (int i = 0; i <= 25; i++) {if (hash1[i] != hash2[i])return false;}return true;}vector<int> findAnagrams(string s, string p) {//两个哟挂数组模拟的哈希表int hash1[26] = {0};int hash2[26] = {0};vector<int> ret;//存储返回值的数组int n = s.size();//限制right++的上限int m = p.size();//p字符串的长度for (auto c : p) {//将p字符串存储进hash2hash2[c - 'a']++;}for (int left = 0, right = 0, count = 0; right < n; right++) {//进窗口int in = s[right] - 'a';//将s中的字符转换为0~25下标hash1[in]++;//将字符存储进hash1 让v值++//窗口过长while (right - left + 1 > m) {//出窗口int out = s[left++] - 'a';//将s中的字符转换为0~25下标hash1[out]--;//从hash1中移除字符 让v值--}//窗口符合要求的时候,去判断记录结果 bool ret2 = check(hash1, hash2);if (ret2 == true)ret.push_back(left);}return ret;}
};

②:优秀版

写法1:

class Solution {
public:vector<int> findAnagrams(string s, string p) {int hash1[26]={0};int hash2[26]={0};vector<int> ret;int n =s.size();int m = p.size();for(auto c:p){hash2[c-'a']++;}for(int left=0,right=0,count =0;right<n;right++){int in = s[right]-'a';if(hash1[in]<hash2[in]) count++;//进窗口前 如果字符频率 < 目标频率 则count++hash1[in]++;while(right-left+1>m){int out = s[left++]-'a';hash1[out]--;if(hash1[out] <hash2[out]) count--;//出窗口后 如果字符频率 < 目标频率 则count++}//有效字符个数count 等于p字符串的长度 则代表是异位词!if(count==m) ret.push_back(left);}return ret;}
};

写法2:

class Solution {
public:vector<int> findAnagrams(string s, string p) {int hash1[26]={0};int hash2[26]={0};vector<int> ret;int n =s.size();int m = p.size();for(auto c:p){hash2[c-'a']++;}for(int left=0,right=0,count =0;right<n;right++){int in = s[right]-'a';hash1[in]++;if(hash1[in]<=hash2[in]) count++;//进窗口后 如果字符频率 <= 目标频率 则count++while(right-left+1>m){int out = s[left++]-'a';hash1[out]--;if(hash1[out] <hash2[out]) count--;//出窗口后 如果字符频率 < 目标频率 则count++}//有效字符个数count 等于p字符串的长度 则代表是异位词!if(count==m) ret.push_back(left);}return ret;}
};

总结二者:

1:两种写法的区别在于,前者是在进窗口之前先判断一下字符频率 < 目标频率?所以如果小于,则代表你这次进窗口是会让count++的!而后者是进窗口之后,再判断字符频率 <= 目标频率?因为你进了之后,才让二者频率相等,则你的count仍然是会++的!

2:我们用数组来模拟哈希表的本质就是,让26个字符对应0~25下标,所以一定要记得-'a'!

3:不用担心字符在进窗口后,此字符在hash2中不存在,因为不存在,则不会通过if语句,不会影响到count

4:不用担心出窗口的字符在hash2中不存在,因为不存在,也不会通过if语句,不会影响到count

http://www.xdnf.cn/news/17546.html

相关文章:

  • 第二十天:数论度量
  • 前端Web在Vue中的知识详解
  • 数据溢出ERROR L107:ADDRESS SPACE OVERFLOW
  • 11. 为什么要用static关键字
  • 【C++】string 的特性和使用
  • Python(13) -- 面向对象
  • 【面试场景题】通过LinkedHashMap来实现LRU与LFU
  • Java+Vue打造的采购招投标一体化管理系统,涵盖招标、投标、开标、评标全流程,功能完备,附完整可二次开发的源码
  • 标准IO实现
  • Effective C++ 条款32:确定你的public继承塑模出 is-a 关系
  • AWT 基本组件深入浅出:Button/Label/TextField/Checkbox/Choice/List 全面实战与性能优化
  • 2025-08-09 李沐深度学习14——经典卷积神经网络 (2)
  • MySQL相关概念和易错知识点(4)(分组查询、连接查询、合并查询、子查询)
  • Mysql笔记-系统变量\用户变量管理
  • 【LLM实战|langchain】langchain基础
  • toRef和toRefs
  • 智慧城管复杂人流场景下识别准确率↑32%:陌讯多模态感知引擎实战解析
  • Easysearch 冷热架构实战
  • Linux下管道的实现
  • SpringBoot 集成 MapStruct
  • 《从零实现哈希表:详解设计、冲突解决与优化》
  • [激光原理与应用-197]:光学器件 - 图解双折射晶体的工作原理
  • Aurora接口FPGA设计
  • C# 异步编程(使用异步Lambda表达式)
  • pdf预览Vue-PDF-Embed
  • C++ 类模板
  • Android MVP架构详解:从理论到实践
  • [优选算法专题一双指针——四数之和]
  • 大语言模型概述
  • 【后端】Java Stream API 介绍