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

Leetcode 找到字符串中所有字母异位词

L

在 C++ 中,两个 vector<int> 类型的变量进行 == 操作时,会逐个比较它们的元素,只有当两个向量的长度相同且每个位置上的元素值都相同时,== 操作才会返回 true

因此,在这道题的代码中,sCount == pCount 这一操作会执行如下步骤:

  1. 长度比较:首先,两个 vector<int> 的长度会进行比较。如果长度不相等,直接返回 false
  2. 元素逐个比较:如果长度相等,那么会逐个比较两个向量对应位置的元素。如果每个位置上的元素值都相同,则返回 true;如果某个位置的元素不相等,则立即返回 false

在这道题中,sCountpCount 的长度始终固定为 26(对应 26 个小写字母),因此不会出现长度不同的情况。== 操作会逐个比较这 26 个元素的值,当 sCountpCount 相等时,意味着当前窗口中的字符频率与字符串 p 中字符的频率完全一致,即找到了一个异位词。

具体执行流程:

  • 对于每次 sCount == pCount,程序将比较两个 vector 的 26 个位置。
  • 如果所有位置的值都相同,说明这两个频率数组是一样的,那么就返回 true
  • 如果有一个位置的值不同,则返回 false

vector== 操作的时间复杂度:

在这个特定例子中,sCountpCount 的长度固定为 26,所以比较操作是常数时间 O(26),即 O(1)。

class Solution {
public:vector<int> findAnagrams(string s, string p) {vector<int> result;//如果s的长度都小于p,那么没有可能的异位词,应该直接返回空结果。if (s.size() < p.size()) return result;//由于字符串仅仅包含小写字母,所以我们可以创建2个元素个数为26的向量来代替hash表//我们可以在这两个向量中统计p的字符频率和s中滑动窗口中的字符频率vector<int> pCount(26, 0);vector<int> sCount(26, 0);//然后分别统计p中的字符频率和第一个滑动窗口中的字符频率for(char c:p) {pCount[c - 'a']++;}int windowSize = p.size();for(int i = 0; i < windowSize; ++i) {sCount[s[i] - 'a']++;}//如果p的字符频率和第一个窗口中的字符频率相同,则将下标0加入到result数组中。if(pCount == sCount) { //当且仅当两个向量中每个位置的每个元素都相同时, == 返回trueresult.push_back(0);}//然后开始滑动窗口, 这里 i 是滑动窗口的最右下标for(int i = windowSize; i < s.size(); ++i) {sCount[s[i] - 'a']++;sCount[s[i - windowSize] - 'a']--; //之前窗口最左边的元素删除if(sCount == pCount) {// i - windowSize + 1 是窗口起始下标,//因为 i - 起始下标 + 1 = windowSize; 所以窗口起始下标 = i - windowSize + 1result.push_back(i - windowSize + 1); }}return result;}
};

算法代码中,最巧妙的地方是通过这两行代码,将字符串p和窗口中的子串每个字符存储在下标相同的数组位置上,这使得可以通过==操作符来判断是否是异位词

pCount[c - 'a']++;sCount[s[i] - 'a']++;

是的,你理解得非常准确!这两行代码是该算法的关键部分之一,使用了字符的 ASCII 码来将字符映射到数组的正确位置上。

具体来说:

  • pCount[c - 'a']++ 中,c - 'a' 计算的是字符 c 到字符 'a' 的偏移量。由于小写字母的 ASCII 码是连续的(‘a’ 的 ASCII 码最小,其他字母依次递增),所以通过 c - 'a' 可以将字母 'a' 对应到数组的索引 0,字母 'b' 对应到索引 1,依次类推直到 'z' 对应到索引 25。
  • pCountsCount 都是长度为 26 的数组,每个元素表示相应字母的出现频率。例如,pCount[0] 代表字符 'a' 在字符串 p 中出现的次数,pCount[1] 代表字符 'b' 的出现次数,依次类推。
  • 对于 sCount[s[i] - 'a']++,它将字符串 s 中当前窗口的字符同样映射到对应的数组位置,统计每个字符在窗口内出现的次数。

巧妙之处:

  1. 数组映射代替哈希表:因为只有 26 个小写字母,所以我们用一个长度为 26 的数组(vector<int>)来代替哈希表,这样避免了复杂的哈希映射,同时通过数组的下标操作可以快速访问字符对应的位置,节省了时间。

  2. 字符频率比较:通过将两个字符串的字符频率存储在数组中的对应位置(下标),我们可以直接利用 == 操作符逐个比较两个频率数组,判断当前窗口中的子串是否是 p 的异位词。这种方式比使用排序或者逐个字符比较更加高效,特别适合字符种类较少的情况。

总结:

这两行代码的巧妙之处在于将字符直接映射到数组下标,从而可以用数组来记录字符出现的频率,并通过简单的数组比较来判断异位词,从而使得算法简洁、高效。

为什么这一行代码的下标是i - windowSize + 1而不是i - windowSize?

            if(sCount == pCount) {// i - windowSize + 1 是窗口起始下标,//因为 i - 起始下标 + 1 = windowSize; 所以窗口起始下标 = i - windowSize + 1result.push_back(i - windowSize + 1); }

这行代码中的下标 i - windowSize + 1 是用来计算异位词的起始位置。让我们一步步来理解为什么这个下标需要加 1

滑动窗口的工作原理:

假设 p 的长度是 windowSize,每次滑动窗口检查的都是一个长度为 windowSize 的子串。

  • i 为当前窗口的最右端下标时,窗口的左端下标就是 i - windowSize + 1,这样窗口的长度恰好为 windowSize

举个例子:

假设字符串 s = "abab", p = "ab",则 windowSize = 2,我们看看当 i 变化时,窗口的边界情况:

  • i = 1 时,当前窗口的字符是 "ab"s[0]s[1]),它是从 i - windowSize + 1 = 1 - 2 + 1 = 0 开始的。
  • i = 2 时,当前窗口的字符是 "ba"s[1]s[2]),它是从 i - windowSize + 1 = 2 - 2 + 1 = 1 开始的。

可以看到,加上 +1 是为了得到窗口的起始位置,因为 i 是当前窗口的最右端索引,而我们需要将这个窗口的最左端索引加入到结果中。

更详细的解释:

  • i 是当前窗口的 最右端 的索引(即当前我们处理的字符的索引)。
  • windowSize 是窗口的长度,所以窗口的左端应该是 i - windowSize + 1
  • 如果我们直接用 i - windowSize,得到的是窗口左端 之前 的一个位置,不是实际的起始位置。因此,需要加 1 来得到准确的左端起始位置。

总结:

i - windowSize + 1 的作用是从当前窗口的最右端 i 推导出该窗口的 左端起始位置,并将这个起始位置存入结果中。这是因为 i 对应的是当前滑动窗口的最右边界,而我们需要的是这个窗口的最左边界(即异位词的起始位置)。


http://www.mrgr.cn/news/28298.html

相关文章:

  • 低成本出租屋5G CPE解决方案:ZX7981PG/ZX7981PM WIFI6千兆高速网络
  • uniapp小程序分享使用canvas自定义绘制 vue3
  • C# 操作Excel的多种方式
  • 系统架构师考试18天极限备考复盘(2024年11月)
  • 【已解决】git push一直提示输入用户名及密码、fatal: Could not read from remote repository的问题
  • MySQL --- 自定义函数获取部门层级名称
  • 研究生招生宣传(2024秋)
  • 12 数组——27. 移除元素 ★
  • 1. TypeScript基本语法
  • Autosar BswM配置-手动建立Swc Port实现自定义模式切换
  • Anaconda安装并配置Python环境
  • STM32外设之LTDC/DMA2D—液晶显示(野火)
  • Zookeeper学习
  • java实现系统文件管理
  • 鸿蒙媒体开发系列01——资源分类访问
  • 深入剖析:C++类对象的内存布局与优化
  • 【C++】——list
  • OJ题-反转链表
  • 利士策分享,家和万事兴:幸福生活的基石
  • Linux 开发工具(vim、gcc/g++、make/Makefile)+【小程序:进度条】-- 详解
  • JVM HotSpot 虚拟机: 对象的创建, 内存布局和访问定位
  • [Golang] Sync
  • 【多线程】深入剖析线程池的应用
  • docker发布redis容器
  • 【 html+css 绚丽Loading 】000050 乾坤合璧轮
  • TryHackMe 第1天 | Introduction to Cyber Security