【优选算法】——leetcode——438.找到字符串中所有字母异位词

目录

1.题目

2.题目理解 

3.算法原理

1.如何快速判断两个字符串是否是异位词

2.解决问题 

暴力求解——>滑动窗口+哈希表

 滑动窗口

利用滑动窗口+哈希表解决问题 

 优化:更新结果的判断条件

4.编程代码 

C++代码

 1.频率统计

2. 双指针

 C语言代码

1.字符频率统计 

2.滑动窗口机制

3.判断窗口大小并维护窗口内字符频率

4.更新结果

C++知识点详解

1. STL(Standard Template Library)

2. 范围 for 循环

3. 字符数组与频率统计

4. 双指针(Sliding Window)技巧

5. 成员函数与类


 

ce6fbd68767d465bbe94b775b8b811db.png

731bd47804784fa2897220a90a387b28.gif

专栏:优选算法

 

1.题目

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

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

提示:

  • 1 <= s.length, p.length <= 3 * 104
  • s 和 p 仅包含小写字母

2.题目理解 

“abc”的异位词 有:abc,acb,bac,bca,cab,cba;

 输出: [0,6](子串的起始索引)

3.算法原理

1.如何快速判断两个字符串是否是异位词

1.两个字符串都按照字符顺序排序,然后比较是否相等               排序+比较     nlogn +n       

2.统计每个字符出现的次数     哈希表遍历比较

2.解决问题 

暴力求解——>滑动窗口+哈希表

因为字符串p 的异位词的⻓度“m”⼀定与字符串p的⻓度相同,所以我们可以在字符串 s 中构
造⼀个⻓度为与字符串 p 的⻓度相同的滑动窗⼝,并在滑动中维护窗⼝中每种字⺟的数量;保持窗口大小一次遍历比较。

 滑动窗口

利用滑动窗口+哈希表解决问题 

可以⽤两个⼤⼩为 26 的数组来模拟哈希表,⼀个来保存 s 中的⼦串每个字符出现的个
数,另⼀个来保存 p 中每⼀个字符出现的个数。这样就能判断两个串是否是异位词。

 优化:更新结果的判断条件

利用变化量count来统计窗口中“有效字符的次数”;

当窗⼝中每种字⺟的数量与字符串p中每种字⺟的数量相同时,则说明当前窗⼝为字符串 p 
的异位词;

 

4.编程代码 

C++代码

class Solution
{
public:
vector<int> findAnagrams(string s, string p)
{
vector<int> ret;
int hash1[26] = { 0 }; // 统计字符串 p 中每个字符出现的个数
for(auto ch : p) hash1[ch - 'a']++;
int hash2[26] = { 0 }; // 统计窗⼝⾥⾯的每⼀个字符出现的个数
int m = p.size();
for(int left = 0, right = 0, count = 0; right < s.size(); right++)
{
char in = s[right];
// 进窗⼝ + 维护 count
if(++hash2[in - 'a'] <= hash1[in - 'a']) count++;
if(right - left + 1 > m) // 判断
{
char out = s[left++];
// 出窗⼝ + 维护 count
if(hash2[out - 'a']-- <= hash1[out - 'a']) count--;
}
// 更新结果
if(count == m) ret.push_back(left);
}
return ret;
}
};

 1.频率统计

for(auto ch : p) hash1[ch - 'a']++;
  • 范围 for 循环: 用于遍历字符串 p,并统计每个字符的出现次数。
  • 字符偏移量 ch - 'a': 将字符转换为数组索引,例如 'a' 对应 0'b' 对应 1

2. 双指针

  • 初始化指针和计数器 left, right, count: leftright 分别表示窗口的左边界和右边界,count 用于记录匹配字符的数量。
  • 滑动窗口 for 循环: 遍历字符串 s,右指针 right 不断向右移动。
  • 进窗口操作: 将 s[right] 加入窗口并更新频率数组 hash2。如果该字符的频率没有超过 hash1 中的频率,则计数器 count 加一。
  • 出窗口操作: 当窗口大小超过 m 时,将 s[left] 移出窗口,并更新频率数组 hash2 和计数器 count
  • 更新结果: 如果 count 等于 m,说明当前窗口是一个字母异位词,记录起始索引 left

 C语言代码

int* findAnagrams(char * s, char * p, int* returnSize) {int s_len = strlen(s);  // 使用 strlen 计算字符串 s 的长度int p_len = strlen(p);  // 使用 strlen 计算字符串 p 的长度int *ret = (int *)malloc(s_len * sizeof(int));*returnSize = 0;int hash1[26] = {0}; // 统计字符串 p 中每个字符出现的个数for (int i = 0; i < p_len; i++) {hash1[p[i] - 'a']++;}int hash2[26] = {0}; // 统计窗口里面的每一个字符出现的个数int count = 0;for (int left = 0, right = 0; right < s_len; right++) {char in = s[right];// 进窗口 + 维护 countif (++hash2[in - 'a'] <= hash1[in - 'a']) {count++;}// 判断窗口是否超过大小if (right - left + 1 > p_len) {char out = s[left++];// 出窗口 + 维护 countif (hash2[out - 'a']-- <= hash1[out - 'a']) {count--;}}// 更新结果if (count == p_len) {ret[*returnSize] = left;(*returnSize)++;}}return ret;
}

1.字符频率统计 

    int hash1[26] = {0}; // 统计字符串 p 中每个字符出现的个数for (int i = 0; i < p_len; i++) {hash1[p[i] - 'a']++;}
  • hash1:长度为26的整数数组,用于统计字符串p中每个字符的出现次数。hash1[i]表示字符'a' + i在字符串p中的出现次数。
  • 循环遍历字符串p,更新hash1数组。

2.滑动窗口机制

    int hash2[26] = {0}; // 统计窗口里面的每一个字符出现的个数int count = 0;for (int left = 0, right = 0; right < s_len; right++) {char in = s[right];// 进窗口 + 维护 countif (++hash2[in - 'a'] <= hash1[in - 'a']) {count++;}
  • hash2:长度为26的整数数组,用于统计滑动窗口内每个字符的出现次数。
  • count:记录当前滑动窗口内与字符串p中字符频率一致的字符数。
  • 滑动窗口通过right指针不断向右移动,将字符s[right]加入窗口,同时更新hash2数组和count计数。

3.判断窗口大小并维护窗口内字符频率

        // 判断窗口是否超过大小if (right - left + 1 > p_len) {char out = s[left++];// 出窗口 + 维护 countif (hash2[out - 'a']-- <= hash1[out - 'a']) {count--;}}
  • 检查滑动窗口的大小,如果窗口大小超过字符串p的长度(right - left + 1 > p_len),则需要移除窗口左侧的字符。
  • 更新left指针,移除窗口左侧字符,同时更新hash2数组和count计数。

4.更新结果

        // 更新结果if (count == p_len) {ret[*returnSize] = left;(*returnSize)++;}}return ret;
}
  • 如果count等于字符串p的长度,说明当前窗口内字符频率与字符串p一致,将left指针的值加入结果数组ret
  • 更新结果数组的大小*returnSize

C++知识点详解

  • STL(Standard Template Library): 向量 vector 是 STL 的一部分,提供动态数组的功能。
  • 范围 for 循环: C++11 引入的循环方式,简化了遍历操作。
  • 字符数组与频率统计: 使用数组来记录字符出现的频率,并进行简单的数学运算实现高效统计。
  • 双指针(Sliding Window)技巧: 通过两个指针控制一个窗口,用于高效地处理子串问题。
  • 成员函数与类: 通过类和成员函数组织代码,方便管理和调用。

1. STL(Standard Template Library)

向量 vector

概述vector 是 C++ 标准模板库(STL)中的一个动态数组,可以根据需要动态调整大小。

特点

  • 动态调整大小:vector 可以在运行时自动扩展和收缩。
  • 随机访问:支持使用索引进行随机访问,访问时间复杂度为 O(1)。
  • 内部实现:使用连续的内存块存储元素,类似于数组。

示例代码

#include <vector>
#include <iostream>int main() {std::vector<int> vec = {1, 2, 3, 4, 5};// 添加元素到末尾vec.push_back(6);// 随机访问std::cout << "Element at index 2: " << vec[2] << std::endl;// 遍历向量for(int i : vec) {std::cout << i << " ";}return 0;
}

高级用法

插入和删除

#include <vector>
#include <iostream>int main() {std::vector<int> vec = {1, 2, 3, 4, 5};// 在指定位置插入元素vec.insert(vec.begin() + 2, 99);// 删除指定位置的元素vec.erase(vec.begin() + 4);// 遍历向量for(int i : vec) {std::cout << i << " ";}return 0;
}

 

 动态调整大小

#include <vector>
#include <iostream>int main() {std::vector<int> vec(5, 10); // 创建一个大小为5的向量,初始值为10vec.resize(10, 20); // 调整向量大小为10,新增的元素值为20for(int i : vec) {std::cout << i << " ";}return 0;
}

2. 范围 for 循环

概述:范围 for 循环是 C++11 引入的一种简化遍历容器的方式。

特点

  • 简化代码:不需要显式地定义迭代器或索引变量。
  • 安全性:自动处理容器的边界,减少越界错误。

示例代码

#include <vector>
#include <iostream>int main() {std::vector<int> vec = {1, 2, 3, 4, 5};for(int i : vec) {std::cout << i << " ";}return 0;
}

 

高级用法

使用引用遍历并修改元素

#include <vector>
#include <iostream>int main() {std::vector<int> vec = {1, 2, 3, 4, 5};for(int& i : vec) {i *= 2; // 每个元素乘以2}for(const int& i : vec) {std::cout << i << " ";}return 0;
}

3. 字符数组与频率统计

概述:字符数组用于存储字符的频率信息,通常用于字符串相关的算法中。

实现:使用大小为 26 的数组来记录每个小写字母的出现次数,数组索引对应字母的偏移量(例如 'a' 对应索引 0,'b' 对应索引 1)。

特点

  • 高效:使用数组进行频率统计和比较,时间复杂度为 O(1)。
  • 简单:数组索引和字符的偏移量直接对应,易于实现和理解。

示例代码

#include <iostream>
#include <string>int main() {std::string s = "hello";int freq[26] = {0};for(char ch : s) {freq[ch - 'a']++;}for(int i = 0; i < 26; ++i) {if (freq[i] > 0) {std::cout << (char)('a' + i) << ": " << freq[i] << std::endl;}}return 0;
}

高级用法

判断两个字符串是否是字母异位词

#include <iostream>
#include <string>
#include <algorithm>bool isAnagram(std::string s1, std::string s2) {if (s1.size() != s2.size()) return false;int freq[26] = {0};for(char ch : s1) {freq[ch - 'a']++;}for(char ch : s2) {freq[ch - 'a']--;}for(int i = 0; i < 26; ++i) {if (freq[i] != 0) return false;}return true;
}int main() {std::string s1 = "listen";std::string s2 = "silent";if (isAnagram(s1, s2)) {std::cout << s1 << " and " << s2 << " are anagrams." << std::endl;} else {std::cout << s1 << " and " << s2 << " are not anagrams." << std::endl;}return 0;
}

4. 双指针(Sliding Window)技巧

概述:双指针技术通常用于处理数组或字符串中的子数组或子串问题。

实现:使用两个指针(左指针和右指针)来维护一个窗口,该窗口在数组或字符串中滑动,以寻找满足特定条件的子数组或子串。

特点

  • 高效:通过调整指针位置来动态维护窗口,减少不必要的计算。
  • 灵活:可以用于解决多种问题,如最长子串、最短子串、子数组和等。

示例代码

#include <vector>
#include <iostream>int main() {std::vector<int> nums = {1, 2, 3, 4, 5, 6};int left = 0, right = 0, sum = 0, target = 10;while(right < nums.size()) {sum += nums[right++];while(sum > target) {sum -= nums[left++];}if(sum == target) {std::cout << "Subarray found from index " << left << " to " << right - 1 << std::endl;}}return 0;
}

高级用法

找到所有和为给定值的子数组

 

#include <vector>
#include <iostream>std::vector<std::pair<int, int>> findAllSubarrays(std::vector<int>& nums, int target) {std::vector<std::pair<int, int>> result;int left = 0, right = 0, sum = 0;while(right < nums.size()) {sum += nums[right++];while(sum > target) {sum -= nums[left++];}if(sum == target) {result.push_back({left, right - 1});}}return result;
}int main() {std::vector<int> nums = {1, 2, 3, 4, 5, 6};int target = 10;std::vector<std::pair<int, int>> subarrays = findAllSubarrays(nums, target);for(auto& p : subarrays) {std::cout << "Subarray found from index " << p.first << " to " << p.second << std::endl;}return 0;
}

5. 成员函数与类

概述:类是 C++ 的基本面向对象编程(OOP)结构,用于封装数据和操作数据的方法。成员函数是类的函数,可以操作类的成员数据。

实现

  • 类定义:使用 class 关键字定义类,类中可以包含数据成员和成员函数。
  • 成员函数:在类的内部定义的方法,可以操作类的成员变量。
  • 访问控制:通过 public, protected, private 控制成员的访问权限。

特点

  • 封装性:将数据和操作封装在一起,提高代码的可维护性和可重用性。
  • 继承性:类可以通过继承复用已有代码。
  • 多态性:通过虚函数实现多态,提高代码的灵活性。

示例代码

#include <iostream>
#include <vector>
#include <string>class Solution {
public:std::vector<int> findAnagrams(std::string s, std::string p) {std::vector<int> ret;int hash1[26] = { 0 };for(auto ch : p) hash1[ch - 'a']++;int hash2[26] = { 0 };int m = p.size();for(int left = 0, right = 0, count = 0; right < s.size(); right++) {char in = s[right];if(++hash2[in - 'a'] <= hash1[in - 'a']) count++;if(right - left + 1 > m) {char out = s[left++];if(hash2[out - 'a']-- <= hash1[out - 'a']) count--;}if(count == m) ret.push_back(left);}return ret;}
};int main() {Solution solution;std::string s = "cbaebabacd";std::string p = "abc";std::vector<int> result = solution.findAnagrams(s, p);for(int index : result) {std::cout << index << " ";}return 0;
}

 

高级用法

类的继承

 

#include <iostream>class Base {
public:void show() {std::cout << "Base class" << std::endl;}
};class Derived : public Base {
public:void show() {std::cout << "Derived class" << std::endl;}
};int main() {Base* basePtr;Derived derived;basePtr = &derived;basePtr->show(); // 调用基类的 show 方法derived.show();  // 调用派生类的 show 方法return 0;
}

虚函数与多态

#include <iostream>class Base {
public:virtual void show() {std::cout << "Base class" << std::endl;}
};class Derived : public Base {
public:void show() override {std::cout << "Derived class" << std::endl;}
};int main() {Base* basePtr;Derived derived;basePtr = &derived;basePtr->show(); // 调用派生类的 show 方法,实现多态return 0;
}

 

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

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

相关文章

MATLAB C++语言编写MEX函数示例:求解本源根

作为一种提升运行效率的手段&#xff0c;MATLAB的MEX函数最初支持C语言编写&#xff0c;从2018a才开始支持基于C11的“现代”C编写MEX&#xff0c;并实现更多现代特性&#xff08;尤其是程序安全性方面&#xff09;。目前&#xff0c;MATLAB官方已不推荐继续用传统C语言编写新的…

【qt小系统】传感器云平台3D散点图(附源码)

摘要&#xff1a;本文主要使用QT5&#xff0c;实现了一个传感器云平台的小示例&#xff0c;模拟的是各类传感器的添加&#xff0c;例如&#xff1a;热成像传感器、温度传感器、超声波传感器&#xff0c;模拟添加完成后&#xff0c;会自动将此传感器的三维坐标增加到3D散点图上&…

Vmware安装openstack

安装虚拟机 创建完成后&#xff0c;点击开启虚拟机 稍等执行成功后 上传压缩包到指定目录。将yoga_patch.tar.gz包上传至/root目录下&#xff0c;将stack3_without_data.tar.gz包使用WinSCP上传至/opt目录下 vim run_yoga.sh #/bin/bash cd /root sudo apt-get update tar -xzv…

添加好友

目录 添加好友的思路&#xff1a; 1.假设A添加B a.如果B在线 b.如果B不在线 添加好友的思路&#xff1a; 1.假设A添加B A给服务端发送要添加B的消息&#xff0c;服务端接受后&#xff0c;先把A添加B的信息存入数据库&#xff0c;再去个人信息表查看B是否在线 a.如果B在线…

「问题解决」jdk高版本导致请求返回对象转换报错

报错&#xff1a;Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make protected native java.lang.Object java.lang.Object.clone() throws java.lang.CloneNotSupportedException accessible: module java.base does not “opens java.lang” to unn…

UnityShaderUI编辑器扩展

前言&#xff1a; 当我们在制作通用Shader的时候&#xff0c;避免不了许多参数混杂在一起&#xff0c;尽管在材质面板已经使用过Header标签来区分&#xff0c;但是较长的Shader参数就会导致冗余&#xff0c;功能块不够简约明了&#xff0c;如图&#xff1a; 对于Shader制作者来…

FPGA开发——蜂鸣器的控制

一、概述 在项目开发的过程当中&#xff0c;我会通常会需要一个东西就行报警显示&#xff0c;有使用语音报警&#xff0c;信息报警等注入此类的方式&#xff0c;但最为简单使用的还是蜂鸣器的使用&#xff0c;蜂鸣器控制简单&#xff0c;成本低&#xff0c;是最为常用的模块之…

测试面试宝典(三十七)—— 接口测试中的加密参数如何处理?

1&#xff09;先了解接口使用的加密方式(md5、rsa...) 2&#xff09;检查接口测试工具是否支持这种加密方式&#xff0c;如果支持的话&#xff0c;直接使用对应功能就行了(比如Jmeter支持md5)&#xff1b;如果加密方式是公司内部特有的算法&#xff0c;可以在接口测试工具中调…

柯尔莫哥洛夫-阿诺德网络

柯尔莫哥洛夫-阿诺德网络&#xff08;Kolmogorov-Arnold Network&#xff0c;简称KAN&#xff09;是一个理论框架&#xff0c;主要用于描述高维函数如何通过一组低维函数的组合来近似表示。这个理论的基础源自安德烈柯尔莫哥洛夫和弗拉基米尔阿诺德在函数逼近理论中的工作&…

NSSRound#4 Team

[NSSRound#4 SWPU]1zweb 考察&#xff1a;phar的反序列化 1.打开环境&#xff0c;审计代码 1.非预期解 直接用file伪协议读取flag,或直接读取flag file:///flag /flag 2.正常解法 用读取文件读取index.php,upload.php的源码 index.php: <?php class LoveNss{publi…

3. Docker的数据管理与持久化

在Docker容器化应用中&#xff0c;数据的持久化和管理是一个关键问题。容器的生命周期短暂&#xff0c;容器的停止和删除会导致数据丢失。因此&#xff0c;了解Docker的数据卷&#xff08;Volumes&#xff09;和挂载&#xff08;Mounts&#xff09;的管理方式&#xff0c;对保障…

环境搭建-Docker搭建ClickHouse

Docker搭建ClickHouse 一、前言二、ClickHouse安装2.1 拉取镜像运行ClickHouse服务 三、测试安装3.1 进入clickhouse容器3.2 命令补充说明 四、测试连接五、设置CK的用户名密码 一、前言 本文使用的Docker使用Windows搭建&#xff0c;Linux版本的搭建方式一样。 Windows系统搭…

Data Race: 并发编程中的数据竞争问题

Data Race: 并发编程中的数据竞争问题 &#x1f50d; &#x1f680; Data Race: 并发编程中的数据竞争问题 &#x1f50d;摘要引言正文内容一、什么是数据竞争&#xff1f; &#x1f914;1.1 数据竞争的定义1.2 数据竞争的特征 二、数据竞争的原因和影响 &#x1f6a8;2.1 原因…

Redis学习笔记——第18章 发布与订阅

第18章 发布与订阅 18.1 频道的订阅与退订 订阅关系保存在字典中&#xff0c;Key为频道&#xff0c;value为订阅该频道的客户端链表 18.2 模式的订阅与退订 可以使用通配符&#xff0c;如果订阅了news.*类型的频道&#xff0c;则会将news.*保存为一个Key&#xff0c;value为…

SpringBoot2整合Kafka

引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency><groupId>org.springframework.kafka</groupId><artifactId>s…

小主机SSD固态硬盘选购攻略,希捷酷鱼 530 SSD固态硬盘表现优秀【附系统无损迁移教程】

小主机SSD固态硬盘选购攻略&#xff0c;希捷酷鱼 530 SSD固态硬盘表现优秀【附系统无损迁移教程】 哈喽小伙伴们好&#xff0c;我是Stark-C~ 这几年随着以零刻为首的小主机市场的兴起&#xff0c;小主机相关的配置周边需求也是越来越大&#xff0c;就比如说SSD固态硬盘就是其…

《Windows API每日一练》22.3 SHE异常

本节我们将讲述单线程到多线程的演进过程&#xff0c;以及进程与线程的区别。 本节必须掌握的知识点&#xff1a; SHE异常 第170练&#xff1a;SEH异常处理程序 第171练&#xff1a;setjmp和longjmp进行异常捕获与处理 22.3.1 SHE异常 在C语言中&#xff0c;Windows平台提供…

定制数据流:在Mojo模型中打造个性化数据预处理

定制数据流&#xff1a;在Mojo模型中打造个性化数据预处理 数据预处理是机器学习工作流程中的关键步骤&#xff0c;它直接影响到模型的性能和训练效率。Mojo模型&#xff0c;作为一个先进的机器学习框架&#xff0c;提供了强大的扩展性来支持自定义数据预处理。本文将深入探讨…

代码随想录算法训练营Day54|| 图论part04

图论部分就先不手写代码了。能理解就很花时间了&#xff0c;先看懂逻辑和代码&#xff0c;关键基础部分写写吧。 卡玛网110字符串接龙&#xff1a;相当于求无向图的最短路径&#xff0c;广搜最合适&#xff0c;因为广搜第一次找到路径一定最短。 广搜就要利用队列&#xff0c;代…