字符串特点
由若干字符(char)构成的有限序列,相当于是字符数组。在C语言中,字符串通过\0
来判定结尾标志,而在C++中,会提供一个string
类,通过string.size()
判定是否到达末尾。
C语言
char a[5] = "abc";
for(int i = 0; a[i] != '\0'; i++)
// Operation
C++
string a = "abc"; // 定义空时,a = ""
for(int i = 0; i < a.size(); i++)
// Operation
注意:每次获取一个字符要判定时,字符外面要用 ’ ’ 括住而不是 " " 括住来与字符判定。
双指针相关题型
-
左右指针替换元素
344. 反转字符串
库函数reverse()
:是左闭右开,本题通过左右指针,实现了一个左闭右闭的反转函数 -
一个字符被多个字符替代,双指针从后往前填充
√ 76、【字符串】剑指 Offer ——05. 替换空格(C++版本)
设置两个独立双指针,由于为一个字符被多个字符替代,可以从后往前填充,每次都移除足够的位置,原字符串末尾位置和新字符串末尾位置各设置一个指针,一次遍历即可完成填充。 -
快慢指针填充元素
√ 77、【字符串】leetcode ——151. 反转字符串中的单词(C++版本)
(1)预处理:对于空格的处理,采用和 203. 移除链表元素 同样的快慢双指针思路,按照首尾无空格,每个单词间有一个空格进行填充。
(2)完成预处理后,先对整体逆置,再对每个单词进行逆置。
字符串旋转题型
-
左右指针实现旋转
344. 反转字符串
库函数reverse()
:是左闭右开,本题通过左右指针,实现了一个左闭右闭的反转函数 -
旋转+2k为单位移动+小于等于k反转判定
75、【字符串】leetcode——541. 反转字符串 II(C++版本)
本题是在反转字符串的基础上,每次以2k为单位进行移动,旋转前k个子串。当移动到后续字符串长度小于等于k时,对其整体反转。 -
先整体逆置,再部分逆置,实现以区域内部元素不旋转,区域与区域之间旋转
77、【字符串】leetcode ——151. 反转字符串中的单词(C++版本)
(1)预处理:对于空格的处理,采用和 203. 移除链表元素 同样的快慢双指针思路,按照首尾无空格,每个单词间有一个空格进行填充。
(2)完成预处理后,先对整体逆置,再对每个单词进行逆置。 -
先部分逆置,再整体逆置,实现区域内部元素不旋转,区域间进行了移动
78、【字符串】剑指 Offer ——58 - II. 左旋转字符串(C++版本)
方法一:使用substr()
函数,先截取后面的字符,再截取前面的字符进行拼接。
方法二:对前面部分和后面部分分别逆置,然后再整体逆置。
KMP题型
KMP理论和实现部分:41、【匹配算法】KMP字符串匹配算法(C/C++版)
KMP算法实现
(1)next不减去一
//const int M = 1e5 + 10, N = 1e6 + 10;
// int next[M];
// char p[M]; // 模式串
// char s[N]; // 目标串
// int m; // 模式串的长度
void getNext(int next[], char p[], int m){
// 初始化
next[0] = 0;
// j为前缀的末尾, i为后缀的末尾。前缀是除最后一个字符,后缀是除第一个字符。
for(int i = 1, j = 0; i < m; i++){
// 每次基于之前已记录的最长相等前后缀的基础上进行对比
// 处理前后缀不相同的情况,退回到之前最长的相等前后缀,基于此再进行延伸对比
while(j > 0 && p[i] != p[j]) j = next[j - 1]; // 存户的长度相对于下标少一个数,跳转时候根据next[j-1]跳转
// 处理前后缀相同的情况
if(p[i] == p[j]) j++;
// 在前缀表做记录
next[i] = j; // 存储最长相等前后缀的长度,而长度相对于下标少一个数,跳转时候根据next[i-1]跳转
}
}
void matching(int next[], char p[], int m, char s[], int n) {
int next[M];
getNext(next, p, m);
for(int i = 0, j = 0; i < n; i++){
// 每次基于已有最长公共前后缀匹配,处理不相同情况
while(j && s[i] != p[j]) j = next[j - 1];
// 处理相同情况
if(s[i] == p[j]) j++;
// 处理匹配成功情况
if(j == m)
// 输出目标串中所有与模式串匹配的起始位置下标
printf("%d ", i - m + 1);
// 为保证最长子序列匹配,因此从后向前进行匹配,将j指向前一个元素的next中的值
}
}
(2)next减去一
//const int M = 1e5 + 10, N = 1e6 + 10;
// int next[M];
// char p[M]; // 模式串
// char s[N]; // 目标串
// int m; // 模式串的长度
void getNext(int next[], char p[], int m){
// 初始化
next[0] = -1;
// j为前缀的末尾, i为后缀的末尾。前缀是除最后一个字符,后缀是除第一个字符。
for(int i = 1, j = -1; i < m; i++){
// 每次基于已有最长公共前后缀匹配,处理不相同情况
while(j >= 0 && p[i] != p[j + 1]) j = next[j]; // next中-1的好处是,跳转时候直接根据当前位置所记录的值跳转
// 处理前后缀相同的情况
if(p[i] == p[j + 1]) j++;
// 在前缀表做记录
next[i] = j; // 因初始化为-1,因此存储的值为最长相等前后缀的长度减去一
}
}
void matching(int next[], char p[], int m, char s[], int n) {
int next[M];
getNext(next, p, m);
for(int i = 0, j = -1; i < n; i++){
// 每次基于已有最长公共前后缀匹配,处理不相同情况
while(j >= 0 && s[i] != p[j]) j = next[j];
// 处理相同情况
if(s[i] == p[j + 1]) j++;
// 处理匹配成功情况
if(j == m - 1){ // j初始为-1,少一个数所以对比为n - 1
// 输出目标串中所有与模式串匹配的起始位置下标
printf("%d ", i - m + 1);
// 为保证最长子序列匹配,因此从后向前进行匹配,将j指向前一个元素的next中的值
// 还未匹配成功,跳转到待对比位置
j = next[j];
}
}
-
使用KMP找到含有模式串的起始位置。
√ 79、【字符串】leetcode ——28. 找出字符串中第一个匹配项的下标(C++版本) -
√ 80、【字符串】leetcode ——459. 重复的子字符串(C++版本)
方法一:移动匹配,用原始字符串与自身拼接,去掉头部和尾部,当能在新字符串中找到原始字符串时,认为含有可构造成原始字符串的子串元素
方法二:暴力解法,
方法三:KMP配合两个结论进行解题:
(1)字符串长度减去最长相等前后缀长度,得到的剩余部分,为可组成序列的最小重复子串,设长度为n’,n’ = n - next[n - 1]。
(2)当n可被n’整除时,说明可由该子串构成整个序列,当不能整除时,说明不能由该子串构成整个序列。