LeetCode 28找出字符串中第一个匹配项的下标
题目链接:28. 找出字符串中第一个匹配项的下标 - 力扣(LeetCode)
要在主串/文本串中查找是否出现过模式串
KMP:当字符串相匹配的过程中,出现不匹配的情况时,可以利用已匹配过的内容避免从头开始匹配
模式串的前缀表:
作用:当出现不匹配时,模式串应该从哪个位置开始重新匹配
定义:记录下标i及i之前的字符串中,最长的相同前后缀的长度
前缀:不包含最后一个字符的所有以第一个字符开头的连续子串
后缀:不包含第一个字符的所有以最后一个字符作为结尾的连续子串
为什么用前缀表(理解!!!)
next数组:前缀表/前缀表统一减1。不涉及原理,只是代码实现有差别
时间复杂度:计算next数组O(m),匹配O(n)。所以KMP是O(m+n)
class Solution {
private:
//前缀表减1操作
void getNext(int* next, const string& s) {
int j = -1;
next[0] = j;
for (int i = 1; i < s.size(); ++i) {
while (j >= 0 && s[i] != s[j + 1]) {
j = next[j];
}
if (s[i] == s[j + 1]) {
j++;
}
next[i] = j;
}
}
public:
int strStr(string haystack, string needle) {
//char *strStr(const char *haystack, const char *needle)
if (needle.size() == 0) return 0;//C库函数strStr()无论haystack是不是空串,只要needle是空串,总是返回haystack的首地址
if (haystack.size() == 0 && needle.size() > 0) return -1;//当haystack是空串,且needle不是空串时,返回null
int* next = new int[needle.size()];
getNext(next, needle);
int j = -1;
for (int i = 0; i < haystack.size(); ++i) {
while (j >= 0 && haystack[i] != needle[j + 1]) {
j = next[j];
}
if (haystack[i] == needle[j + 1]) {
j++;
}
if (j == needle.size() - 1) {
return (i - needle.size() + 1);
}
}
delete[]next; next = NULL;
return -1;
}
};
class Solution {
private:
//前缀表不减1操作
void getNext(int* next, const string& s) {
int j = 0;
next[0] = j;
for (int i = 1; i < s.size(); ++i) {
while (j >= 1 && s[i] != s[j]) {
j = next[j - 1];
}
if (s[i] == s[j]) {
j++;
}
next[i] = j;
}
}
public:
int strStr(string haystack, string needle) {
//char *strStr(const char *haystack, const char *needle)
if (needle.size() == 0) return 0;//C库函数strStr()无论haystack是不是空串,只要needle是空串,总是返回haystack的首地址
if (haystack.size() == 0 && needle.size() > 0) return -1;//当haystack是空串,且needle不是空串时,返回null
int* next = new int[needle.size()];
getNext(next, needle);
int j = 0;
for (int i = 0; i < haystack.size(); ++i) {
while (j >= 1 && haystack[i] != needle[j]) {
j = next[j - 1];
}
if (haystack[i] == needle[j]) {
j++;
}
if (j == needle.size()) {
return (i - needle.size() + 1);
}
}
delete[]next; next = NULL;
return -1;
}
};
LeetCode 459重复的子字符串
题目链接:459. 重复的子字符串 - 力扣(LeetCode)
移动匹配
将字符串s拼接为t=s+s
将t掐头去尾
在t里寻找是否出现s
//写法一
class Solution {
public:
bool repeatedSubstringPattern(string s) {
string t = s + s;
t.erase(t.begin());t.erase(t.end() - 1);
return t.find(s) != string::npos;//找不到只能返回npos,这里判断只能和npos对比!!!!
//std::string的方法find,返回值类型是std::string::size_type,对应的是查找对象在字符串中的位置。如果未找到,该返回值是一个很大的数据,判断时与std::string::npos进行对比
}
};
//写法二
class Solution {
public:
bool repeatedSubstringPattern(string s) {
string t = s + s;
return t.find(s, 1) != s.size();//没有掐头去尾的操作所以一定能找到,就不用npos对比了,只要找到的位置不是第二个子串的开始位置即可
}
};
利用KMP
若存在,则重复的子字符串为最长相同前后缀所不包含的子串
class Solution {
private:
//前缀表不减一
void getNext(int* next, const string& s) {
int j = 0;
next[0] = j;
for (int i = 1; i < s.size(); ++i) {
while (j > 0 && s[i] != s[j]) {
j = next[j - 1];
}
if (s[i] == s[j]) {
j++;
}
next[i] = j;
}
}
public:
bool repeatedSubstringPattern(string s) {
if (s.size() == 0) return false;
int len = s.size();
int* next = new int[len];
getNext(next, s);
if (next[len - 1] == 0) return false;
else if (len % (len - next[len - 1]) == 0) return true;
return false;
}
};
字符串总结
能够自己实现substr、reverse等库函数
双指针法的灵活使用:譬如剑指 Offer 05. 替换空格 - 力扣(LeetCode),大大节省了时间效率
反转字符串时,先整体再局部反转的思想:譬如151. 反转字符串中的单词 - 力扣(LeetCode)、剑指 Offer 58 - II. 左旋转字符串 - 力扣(LeetCode)
KMP的使用一定要理解并且熟记