文章目录
- 28. 找出字符串中第一个匹配项的下标
- 题目描述
- 暴力
- KMP算法
28. 找出字符串中第一个匹配项的下标
题目描述
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。
示例 1:
输入:haystack = “sadbutsad”, needle = “sad”
输出:0
解释:“sad” 在下标 0 和 6 处匹配。
第一个匹配项的下标是 0 ,所以返回 0 。
示例 2:
输入:haystack = “leetcode”, needle = “leeto”
输出:-1
解释:“leeto” 没有在 “leetcode” 中出现,所以返回 -1 。
提示:
- 1 <= haystack.length, needle.length <= 104
- haystack 和 needle 仅由小写英文字符组成
暴力
// 定义Solution类
class Solution {
public:// strStr函数接受两个字符串参数:haystack(主字符串)和needle(子串)int strStr(string haystack, string needle) {// 初始化count变量来跟踪匹配的连续字符数int count=0;// 初始化k变量来跟踪needle字符串中的当前位置int k=0;// 遍历haystack字符串for(int i=0; i<haystack.size(); i++){// 如果当前字符匹配,增加count并移动到needle的下一个字符if(haystack[i] == needle[k]){count++;k++;// 打印出当前的匹配长度(主要用于调试)cout << count << endl;// 如果全部字符都匹配,返回第一个匹配字符的下标if(count == needle.size())return i - count + 1;}// 如果当前字符不匹配else{// 重置i为上一个匹配序列的开始的下一个位置i = i - count;// 重置count和k为0,重新开始匹配count = 0;k = 0;}}// 如果遍历了整个haystack都没有找到完全匹配的needle,返回-1return -1;}
};
重要的代码段解释:
- int i=0; i<haystack.size(); i++: 这个循环遍历主字符串haystack。
- if(haystack[i] == needle[k]): 检查当前haystack的字符是否与needle的当前字符匹配。
- count++; k++;: 如果匹配,增加count(匹配的字符数)并且k增加,使得下一次循环检查needle的下一个字符。
- if(count == needle.size()): 如果count等于needle的长度,说明已经找到一个完全的匹配,返回第一个匹配的下标。
- else分支: 当一个不匹配发生时,代码会重置i到当前检查序列的开始的下一个字符,并重置count和k为0,意味着重新开始匹配。
需要注意的是,这段代码的性能并不是最优的,因为在发现一个字符不匹配时,它会回溯到上一个匹配序列的开始后面一个字符重新开始匹配,可能会导致多次重复检查同一个字符。更高效的字符串匹配算法如KMP算法能够在不回溯主字符串的情况下继续匹配。
KMP算法
不懂KMP算法的看这篇文章:28. 实现 strStr()
构造next数组图解
// 定义解决方案类
class Solution {
public:// strStr成员函数,接受两个字符串:haystack(搜索范围)和needle(目标字符串)int strStr(string haystack, string needle) {// 定义一个数组next用于存储KMP算法中的部分匹配表int next[needle.size()];// 计算needle的部分匹配表getnext(next, needle);//得到next数组// 定义一个指针j,用于指向needle的当前匹配位置//这里的i和j理解和下面getnext函数中类似//因为要回退的是j,所以j对应的是needle//所以i对应haystackint j=0;// 遍历haystack字符串for(int i=0; i<haystack.size(); i++) {// 使用while循环处理不匹配的情况// 如果j大于0且当前字符不匹配,则回退j到部分匹配的位置while(j > 0 && haystack[i] != needle[j]) {j=next[j-1];}// 如果当前字符匹配,则指针j递增if(haystack[i] == needle[j]) {j++;}// 如果j的值等于needle的长度,则表明找到了完整匹配// 返回匹配的起始下标if(j == needle.size()) {return i - needle.size() + 1;}}// 如果没有找到匹配,返回-1return -1;}private:// getnext函数用于计算KMP算法中的部分匹配表void getnext(int *next, const string& s) {// 初始化j为0,j指向前缀的末尾位置int j=0;// next数组的第一个元素总是0next[0]=0;//0的位置也是回退到0// 使用for循环计算next数组的其余部分for(int i=1; i<s.size(); i++) {//要比较前后缀是否相等,i从1开始,且i最后指向后缀末尾位置(s.size()-1)// 如果前后缀不相同,回退j的位置while(j > 0 && s[i] != s[j]) {j=next[j-1];//j进行回退}// 如果前后缀相同,j递增if(s[i] == s[j]) {j++;}// 更新next数组//更新分两种情况://第一种:相等时就更新//第二种:不相等时,j回退到0时更新为0next[i] = j;}}
};
这段代码中的KMP算法包含两个主要部分:
-
getnext()函数计算部分匹配表(也就是next数组)。部分匹配表是KMP算法的核心,它记录了模式字符串needle中的前后缀的最长公共元素的长度。在搜索过程中不匹配时,可以利用这个信息跳过一些不必要的比较。
-
strStr()函数使用部分匹配表来加速搜索过程。当出现不匹配的情况时,不用像暴力搜索那样从头开始比较,而是根据next数组回退到某一位置继续比较。