2.0字符串篇
关于string和cstring
<string>
和 <cstring>
是 C++ 中的两个不同的头文件,它们有不同的作用和功能:
<string>
头文件包含了 C++ 标准库中的字符串类std::string
,以及与字符串相关的一些函数和操作符重载。这个头文件用于处理 C++ 中的字符串操作,提供了更多的字符串处理功能和便利。<cstring>
头文件是 C 标准库中的头文件,其中定义了一些 C 语言字符串操作的函数,比如字符串复制、连接、比较等。在 C++ 中,这些函数通常被包含在std
命名空间中,比如std::strcpy
、std::strcat
等。
总的来说,<string>
更适合用于 C++ 中的字符串操作,而 <cstring>
则更适合用于 C 风格的字符串操作。
关于string包含的常见函数
length()
或size()
: 返回字符串的长度。append(str)
: 在字符串末尾添加另一个字符串。insert(pos, str)
: 在指定位置插入另一个字符串。erase(pos, len)
: 从指定位置删除指定长度的字符。find(str)
: 查找字符串中是否包含指定的子字符串。substr(pos, len)
: 返回从指定位置开始的指定长度的子字符串。compare(str)
: 比较两个字符串。replace(pos, len, str)
: 用另一个字符串替换指定位置的指定长度的字符。push_back()、pop_back()
:类似数组的操作方式,对字符串依然使用。
异或:^
.运算规则:N^0=N;N^N=0;
判断大小写:islower
、isupper
1 | class Solution { |
转为小写:tolower
/转为大写:toupper
(注意,返回值类型为int。可以写作char ch = tolower(c);
或者auto ch = char(tolower(c));
)。如果是数字,则无变化,可以接受数字
检查一个字符是否是字母或数字:isalnum
,需要头文件<cctype>
.如果判断是否为字母:isalpha
,判断是否为数字:isdigit
.也是需要头文件<cctype>
定义b为a的逆序字符串:
1 | string b(a.rbgin(),a.rend()); |
1.回文串的定义
验证回文串:125
1 | class Solution { |
空间优化:使用双指针,在原字符串上直接判断
1 | class Solution { |
取字符串str从索引 0 开始、长度为 index 的子字符串:(注意包括的是str[0]到str[i-1]的部分!)
1 | str.substr(0, index); |
2.公共前缀
最长公共前缀:14
1 | //纵向扫描 |
3.单词
判断单词数:434
关键在于看该字符的前一个字符s[i-1]是否为空(这样可以遍历到所有字符而且不需要额外判断),而不是看下一个字符s[i+1]是否为空
1 | class Solution { |
4.字符串的反转
反转字符串s
1 | void reverseString(vector<char>& s) { |
注:关于min(),两个参数都必须是int类型,而s.size()是unsigned int类型,需要转换!(或者一开始就写成int n=s.size()
,然后后面用n代替)
1 | reverse(s.begin() + i, s.begin() + min(i + k, (int)s.size())); |
反转字符串中的各个单词:557(进阶:包括前置后置以后中间与多个空格时,stringstream能够很好解决,只不过用到了额外空间ans和t并非原地解法,时间复杂度为O(n * m + k),空间复杂度为O(n)。原地解法应该用双指针)
1 | string reverseWords(string s) { |
5.统计字符串(哈希表)
5-1.字符串中的第一个唯一字符:387
1 | int firstUniqChar(string s) { |
还可以用find()和rfind():(空间复杂度为$O(1)$,时间复杂度$O(n^2)$)
1 | class Solution { |
优化:记录频数改为记录索引
1 | class Solution { |
注:(用纯数组更快,尽管时间复杂度和空间复杂度相同,但是哈希表牵涉到建表、哈希碰撞等等)
方法二:使用队列queue(时间空间复杂度同法一)
1 | class Solution { |
emplace()
是 C++ 中容器类的一个成员函数,用于在容器中就地构造元素,而不是通过拷贝或移动构造函数。
对于容器类(如 std::vector
、std::map
、std::unordered_map
等),emplace()
函数接受构造函数的参数,并在容器中直接构造一个新的元素,而不是将一个已经构造好的对象插入容器。
这种就地构造的方式可以避免额外的拷贝或移动操作,提高代码的效率。它对于大型对象或者不可复制/移动的对象特别有用。
注:关于哈希表
统计出现次数(时间复杂度$O(1)$):
1 | position.count(s[i]); |
内部定义:
1 | unordered_map<char, int> mp = { |
遍历元素:
1 | for (auto& [_, pos] : position) { |
这段代码使用了 C++17 中的结构化绑定(structured bindings)语法,用于遍历名为 position
的容器中的元素。遍历的顺序并不是unordered_map中元素的插入顺序,由于unordered_map没有保证元素的插入顺序,遍历顺序为乱序,从而影响结果。这是因为 std::unordered_map
使用哈希表实现,元素的存储位置是根据哈希值计算得到的,而不是按照插入顺序或其他特定顺序。
在每次循环迭代中,auto& [_, pos]
将容器中的键值对解构为两个变量 _
和 pos
。这里使用 _
表示不需要使用的变量,而 pos
则表示容器中的值。
这两个for循环等价:
1 | for(auto& [_,strArray]:mp){ |
1 | for (auto it = mp.begin(); it != mp.end(); ++it) { |
哈希表中的每个元素的类型为pair
1 | unordered_map<char, int> mp; |
自定义比较方法(这里second值大的排在前面)
1 | sort(cnt.begin(), cnt.end(), |
5-2.字符串找不同:389、242
一、计数:首先遍历字符串 s,对其中的每个字符都将计数值加 1;然后遍历字符串 t,对其中的每个字符都将计数值减 1。也可以使用哈希表,但法一空间复杂度较高。(如果是Unicode字符就老老实实用这种方法)
1 | class Solution { |
二、求和:将字符串 s 中每个字符的 ASCII 码的值求和,得到 $A_s$;对字符串 t 同样的方法得到 $A_t$。两者的差值 $A_t-A_s$ 即代表了被添加的字符。
三、位运算:将两个字符串拼接成一个字符串,则问题转换成求字符串中出现奇数次的字符。(136题也是位运算,用到了异或^
)
1 | class Solution { |
因为在s和t之中都出现的ch,在异或运算之后变成了0,相当于ret^s[0]^s[1]...^t[0]^t[1]...=0^(s[0]^t[0])^(s[1]^t[1])^...^t[more]=0^0^0^...^0^t[more]=t[more].
(这里假设s[0]==t[0],s[1]==t[1]…)
容器角度一般的优化思路。完整的是多容器->多次新建容器->单容器->数组->二进制位。
5-3.字母异位词分组:49
法一:排序(由于互为字母异位词的两个字符串包含的字母相同,因此对两个字符串分别进行排序之后得到的字符串一定是相同的,故可以将排序之后的字符串作为哈希表的键。)可以不用sort()来排序以节省时间开销,但是可能增加空间开销。
1 | class Solution { |
法二:计数(将每个字母出现的次数使用字符串表示,作为哈希表的键。时间复杂度更低,空间复杂度更高)
5-4.根据字符串出现频率排序:451
桶排序:速度比sort()快
1.遍历字符串,统计每个字符出现的频率,同时记录最高频率 maxFreq;
2.创建桶,存储从 1 到 maxFreq 的每个出现频率的字符;
3.按照出现频率从大到小的顺序遍历桶,对于每个出现频率,获得对应的字符,然后将每个字符按照出现频率拼接到排序后的字符串。
5-5.求连续递增子串的个数,并去重:467
动态规划
1 | class Solution { |
5-6.URL的解密与加密:535
如果想要实现相同的 URL 加密成同一个 TinyURL,则额外保存一个从 URL到 TinyURL 的映射。
注:int->string:to_string(intVar);
string->int:stoi(stringVar);
关于随机数
time(0)
函数返回当前的系统时间(从某一时刻开始的秒数),而srand
函数是用来设置随机数生成器的种子。将time(0)
的返回值传递给srand
函数,可以使用当前时间作为随机数生成器的种子,从而使得每次程序运行时生成的随机数序列都不同。
在C++中,通常在使用随机数生成函数rand
之前,会调用srand(time(0))
来初始化随机数生成器。这样可以确保每次程序运行时都会生成不同的随机数序列,因为种子不同会导致不同的随机数序列。之后就可以使用randomNumber=rand();