一、题目
给你两个字符串
haystack
和needle
,请你在haystack
字符串中找出needle
字符串的第一个匹配项的下标(下标从 0 开始)。如果needle
不是haystack
的一部分,则返回-1
这个题目其实是在实现string的内置函数find()。find()函数用的很广,需要包含<algorithm>,主要用在查找容器的某个对象,返回值是迭代器,用在string中,主要用于查找指定字符和子串,返回值是下标。
可以使用字符串匹配算法解决,常见的字符串匹配算法包括暴力匹配、Knuth-Morris-Pratt 算法、Boyer-Moore 算法、Sunday 算法等。
二、暴力匹配
这是leetcode中的简单题,我首先想到的就是“暴力解法”,即通过遍历判断是否符合题意,如果找到了就返回下标,没找到就跳出循环。思路较简单,下面是我的1.0版代码(不能正确运行)。
在执行string haystack= "leetcode";string needle = "leeto";时出现错误,本应该输出-1,但却输出了5。代码没有语句错误,逻辑错误很难找出来,在这里我花了很长时间找错。
class Solution {
public:
int strStr(string haystack, string needle) {
int num1=strlen(haystack.c_str());
int num2=strlen(needle.c_str());
for(int i=0;i<num1;i++){
int temp=i;
for(int j=0;j<num2;j++){
if(haystack[temp]==needle[j]){
temp++;
}
else{
continue;
}
if(j==num2-1){
return i;
}
}
}
return -1;
}
};
最后发现,需要将continue变成break。break用于完全结束一个循环,跳出循环体执行循环后面的语句,而continue用于跳过本次循环,直接进入下一次循环,continue不会结束整个循环的执行,而是结束本次循环,继续执行后续的循环语句在此错误使用continue的话,会造成资源浪费和判断不准确。
下面是2.0版本代码(运行成功)。
class Solution {
public:
int strStr(string haystack, string needle) {
int num1=strlen(haystack.c_str());
int num2=strlen(needle.c_str());
for(int i=0;i<num1;i++){
int temp=i;
for(int j=0;j<num2;j++){
if(haystack[temp]==needle[j]){
temp++;
}
else{
break;
}
if(j==num2-1){
return i;
}
}
}
return -1;
}
};
以下是运行效率。这个执行用时这么短是我没想到的。。
三、Knuth-Morris-Pratt (KMP)算法
KMP?KFC?作为小白,确实没听过这个算法。相对文字,本人更喜欢视频,下面推荐一个B站大学的视频。
【最浅显易懂的 KMP 算法讲解】 https://www.bilibili.com/video/BV1AY4y157yL/?share_source=copy_web&vd_source=1c40518818373e585c08ba3456deee48
本网站中也有很多佬写了算法解析,这位博主写得很清晰。
了解了算法原理,那么我们就能用优化方法解该题了。
解析过程比较复杂,参考leetcode官网,讲得很详细,图文并茂。
代码如下。
class Solution {
public:
int strStr(string haystack, string needle) {
int n = haystack.size(), m = needle.size();
if (m == 0) {
return 0;
}
vector<int> pi(m);
for (int i = 1, j = 0; i < m; i++) {
while (j > 0 && needle[i] != needle[j]) {
j = pi[j - 1];
}
if (needle[i] == needle[j]) {
j++;
}
pi[i] = j;
}
for (int i = 0, j = 0; i < n; i++) {
while (j > 0 && haystack[i] != needle[j]) {
j = pi[j - 1];
}
if (haystack[i] == needle[j]) {
j++;
}
if (j == m) {
return i - m + 1;
}
}
return -1;
}
};
作者:力扣官方题解
链接:https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/solutions/732236/shi-xian-strstr-by-leetcode-solution-ds6y/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
讲真,上面这玩意儿,不理解KMP算法真的很难搞懂。
四、Sunday算法
第一步,搞懂原理。不得不说,这些首次提出一个优化算法的大佬真厉害。下面是一个博主讲的算法原理。
直接上代码了。
class Solution {
public:
int strStr(string haystack, string needle) {
if(needle.empty())
return 0;
int slen=haystack.size();
int tlen=needle.size();
int i=0,j=0;//i指向源串首位 j指向子串首位
int k;
int m=tlen;//第一次匹配时 源串中参与匹配的元素的下一位
for(;i<slen;)
{
if(haystack[i]!=needle[j])
{
for(k=tlen-1;k>=0;k--)//遍历查找此时子串与源串[i+tlen+1]相等的最右位置
{
if(needle[k]==haystack[m])
break;
}
i=m-k;//i为下一次匹配源串开始首位 Sunday算法核心:最大限度跳过相同元素
j=0;//j依然为子串首位
m=i+tlen;//m为下一次参与匹配的源串最后一位元素的下一位
if(m>slen)//当下一次参与匹配的源串字数的最后一位的下一位超过源串长度时
return -1;
}
else
{
if(j==tlen-1)//若j为子串末位 匹配成功 返回源串此时匹配首位
return i-j;
i++;
j++;
}
}
return -1;//当超过源串长度时
}
};
作者:西野七瀬
链接:https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/solutions/6920/c5chong-jie-fa-ku-han-shu-bfkmpbmsunday-by-2227/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。