力扣28:实现strStr()
[描述]:
在模式串 pattern 中查找是否含有子串 substr,若有,返回子串第一次出现的位置,反之,返回 -1。
思考问题:若是 substr 为空时应该返回什么呢?
显然,这是一道KMP算法应用的题目。
kmp算法介绍
第一种解法:暴力解法
我们可以让字符串 pattern与字符串 substr 的所有长度为 m 的子串均匹配一次。
为了减少不必要的匹配,我们每次匹配失败即立刻停止当前子串的匹配,对下一个子串继续匹配。如果当前子串匹配成功,我们返回当前子串的开始位置即可。如果所有子串都匹配失败,则返回 −1。
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
/*
* Author: 酒馆店小二
* Description: 28.实现strStr()
* Date: 2022-02-19 13:51:56 星期六
* FileName: leetcode28.cpp
* Location: D:\VSCODE_CPP\algorithm\kmp\leetcode28.cpp
*/
int violentMatch(const string &pattern, const string &substr) {
int pLen = pattern.size();
int sLen = substr.size();
int i = 0, j = 0; // i、j 分别指向 pattern 和 substr 的下标
while (i < pLen && j < sLen) {
if (pattern[i] == substr[j]) { // 当前字符匹配成功
i++;
j++;
} else { // 当前字符匹配失败
i = i - j + 1;
j = 0;
}
}
if (j == sLen) { // 匹配成功,返回子串在模式串中的位置,否则返回 -1
return i - j; // 此处与kmp算法处理不同,原因详见第二种解法
} else {
return -1;
}
}
int main(int argc, char* argv[]){
string pattern = "abcdab";
string substr = "cda";
cout << "Match position is: " << violentMatch(pattern, substr) << endl;
return 0;
}
- 时间复杂度:O(n×m),其中 n 是字符串 pattern 的长度,m 是字符串 substr 的长度。最坏情况下我们需要将字符串 substr 与字符串 pattern 的所有长度为 m 的子串均匹配一次。
- 空间复杂度:O(1)。我们只需要常数的空间保存若干变量。
第二种解法:kmp算法
kmp算法核心是匹配表。
vector<int> kmpNext(const string &substr) { // KMP next 数组
int n = substr.size();
vector<int> next(n);
next[0] = 0;
for (int i = 1, j = 0; i < n; ++i) { // 注意 i 从1开始
// 要保证 j > 0,因为会用到 j - 1 做下标
while (j > 0 && substr[i] != substr[j]) { // 前后缀不相同
j = next[j - 1]; // 向前回退
}
if (substr[i] == substr[j]) { // 找到相同的前后缀
j++;
}
next[i] = j; // 将 j(前缀的长度) 赋给next[i]
}
return next;
}
有了匹配表,就可以使用kmp算法了。
如何判断在模式串中出现了子串?
如果子串的下标指向了子串的末尾,就说明子串完全匹配于模式串中的某个子串了。
本题要求返回模式串中子串出现的第一个字符的位置,所以返回当前在模式串匹配子串的最后一个位置 i ,再减去子串的长度,就可以得到模式串中出现子串第一个字符的位置。
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
/*
* Author: 酒馆店小二
* Description: 28.实现strStr()
* Date: 2022-02-19 13:51:56 星期六
* FileName: leetcode28.cpp
* Location: D:\VSCODE_CPP\algorithm\kmp\leetcode28.cpp
*/
vector<int> kmpNext(const string &substr) { // KMP next 数组
int n = substr.size();
vector<int> next(n);
next[0] = 0;
for (int i = 1, j = 0; i < n; ++i) { // 注意 i 从1开始
// 要保证 j > 0,因为会用到 j - 1 做下标
while (j > 0 && substr[i] != substr[j]) { // 前后缀不相同
j = next[j - 1]; // 向前回退
}
if (substr[i] == substr[j]) { // 找到相同的前后缀
j++;
}
next[i] = j; // 将 j(前缀的长度) 赋给next[i]
}
return next;
}
int kmpMatch(const string &pattern, const string &substr) { // KMP匹配法
// 当 substr是空字符串时,应当返回 0
if (substr.size() == 0) {
return 0;
}
int pLen = pattern.size();
int sLen = substr.size();
vector<int> next = kmpNext(substr);
for (int i = 0, j = 0; i < pLen; i++) {
// 仍然要保证 j > 0,因为会用到 j - 1 做下标
while (j > 0 && pattern[i] != substr[j]) {
j = next[j - 1]; // 注意此处,寻找上一个字符对应的匹配表的位置
}
if (pattern[i] == substr[j]) { // 匹配,i 和 j 同时加 1
j++;
}
if (j == sLen) { // 模式串中出现了子串
// 在第一种暴力方法中此处是 i - j
// 二者不一样是因为第一种方法中 i 做了 ++ 操作,所以此处是 i - j + 1
return i - j + 1;
}
}
return -1;
}
int main(int argc, char* argv[]){
string pattern = "abcdab";
string substr = "cda";
cout << "Match position is: " << kmpMatch(pattern, substr) << endl;
return 0;
}
修改于:2022.02.19
给岁月以文明,给时光以生命