找出字符串中第一个匹配项的下标
· 阅读需 8 分钟
力扣面试经典——28题
💡 参考代码:
/**
* 在haystack字符串中查找needle字符串的第一个匹配位置
* @param haystack 主字符串
* @param needle 要查找的子字符串
* @return 返回第一次匹配的索引,如果未找到则返回-1
*/
int strStr(char* haystack, char* needle) {
// 边界条件处理
if (needle == NULL || *needle == '\0') {
return 0; // 空字符串在任何字符串的索引0处匹配
}
if (haystack == NULL || *haystack == '\0') {
return -1; // 主字符串为空但要查找的字符串非空,返回-1
}
int hLen = strlen(haystack); // 主字符串长度
int nLen = strlen(needle); // 子字符串长度
// 如果子字符串比主字符串长,不可能匹配
if (nLen > hLen) {
return -1;
}
// 构建next数组(KMP算法的核心)
int* next = (int*)malloc(sizeof(int) * nLen);
next[0] = 0; // 第一个字符的最长相等前后缀长度为0
int j = 0; // 前缀末尾索引
// 构造next数组
for (int i = 1; i < nLen; i++) {
// 当前后缀不匹配时,回退到前一个位置的next值
while (j > 0 && needle[i] != needle[j]) {
j = next[j - 1];
}
// 如果当前字符匹配,前缀长度加1
if (needle[i] == needle[j]) {
j++;
}
next[i] = j; // 记录当前位置的最长相等前后缀长度
}
// KMP匹配过程
j = 0; // needle的索引
for (int i = 0; i < hLen; i++) {
// 当字符不匹配时,根据next数组回退
while (j > 0 && haystack[i] != needle[j]) {
j = next[j - 1];
}
// 如果字符匹配,移动needle的指针
if (haystack[i] == needle[j]) {
j++;
}
// 如果needle已经完全匹配,返回匹配起始位置
if (j == nLen) {
free(next); // 释放内存
return i - nLen + 1;
}
}
free(next); // 释放内存
return -1; // 未找到匹配
}
📖 总结:
点击展开题目总结
🤔 找出字符串中第一个匹配项的下标
1️⃣ 题目核心信息
🎯 功能描述:在主字符串(haystack)中查找子字符串(needle)第一次出现的位置,若不存在则返回-1
📥 输入输出:
- 输入:
haystack
:主字符串,被搜索的字符串needle
:模式字符串,需要查找的子字符串
- 输出:返回
needle
在haystack
中第一次出现的索引位置,如果不存在则返回-1
2️⃣ 实现原理
💡 核心思路:使用KMP(Knuth-Morris-Pratt)字符串匹配算法,通过预处理模式串构建next数组来避免不必要的字符比较
📋 实现步骤:
- 处理边界情况:空字符串、主串为空等情况
- 构建next数组:记录模式串中每个位置的最长相等前后缀长度
- 使用KMP算法进行匹配:利用next数组避免主串指针回溯
- 返回匹配结果:找到匹配则返回起始位置,否则返回-1
3️⃣ 关键点解析
🎯 代码技巧
- KMP算法应用:通过构建next数组实现高效的字符串匹配
- 双指针技术:使用两个指针分别遍历主串和模式串
- 状态回退优化:利用next数组实现匹配失败时的智能回退,避免重复比较
4️⃣ 使用场景
✅ 适用情况:
- 在大文本中查找特定子串的位置
- 需要高效字符串匹配的场景
- 文本编辑器的查找功能实现
⚠️ 前提条件:
- 输入参数为有效的C风格字符串
- 字符串以'\0'结尾
5️⃣ 复杂度分析
-
⏱️ 时间复杂度:O(n + m),其中n是主串长度,m是模式串长度,避免了暴力算法的O(n*m)复杂度
-
💾 空间复杂度:O(m),主要用于存储next数组
6️⃣ 注意事项
🚩 边界情况:
- needle为空字符串时应返回0
- haystack为空字符串但needle非空时应返回-1
- needle长度大于haystack时直接返回-1
💥 易错点:
- next数组的构建逻辑容易出错,特别是回退条件的判断
- 匹配成功后的返回索引计算容易错误,应返回i - nLen + 1
- 内存管理需要注意,使用完next数组后需要释放内存
7️⃣ 注意事项
KMP算法原理简述
KMP算法
是一种改进的字符串匹配算法,它的核心思想是当字符匹配失败时,利用已经匹配的部分信息,尽可能地跳过一些不必要的比较。
第一步:边界条件处理
if (needle == NULL || *needle == '\0') {
return 0; // 空字符串在任何字符串的索引0处匹配
}
if (haystack == NULL || *haystack == '\0') {
return -1; // 主字符串为空但要查找的字符串非空,返回-1
}
这部分处理特殊情况:
- 如果要查找的字符串为空,则在任何字符串的第0个位置都能找到它
- 如果主字符串为空但要查找的字符串不为空,则肯定找不到
第二步:获取字符串长度并比较
int hLen = strlen(haystack); // 主字符串长度
int nLen = strlen(needle); // 子字符串长度
if (nLen > hLen) {
return -1;
}
如果要查找的字符串比主字符串还长,那肯定找不到。
第三步:构建next数组(KMP算法的核心)
int* next = (int*)malloc(sizeof(int) * nLen);
next[0] = 0; // 第一个字符的最长相等前后缀长度为0
int j = 0; // 前缀末尾索引
// 构造next数组
for (int i = 1; i < nLen; i++) {
// 当前后缀不匹配时,回退到前一个位置的next值
while (j > 0 && needle[i] != needle[j]) {
j = next[j - 1];
}
// 如果当前字符匹配,前缀长度加1
if (needle[i] == needle[j]) {
j++;
}
next[i] = j; // 记录当前位置的最长相等前后缀长度
}
我们通过一个具体例子来理解next数组的构建过程。假设needle = "ababa"
:
i | needle[i] | j | next[i] | 说明 |
---|---|---|---|---|
0 | a | 0 | 0 | 初始值 |
1 | b | 0 | 0 | 'a' != 'b',j保持0 |
2 | a | 1 | 1 | 'b' != 'a',j回退到next[0]=0,然后'a' == 'a',j=1 |
3 | b | 2 | 2 | 'a' != 'b',j回退到next[1]=0,然后'b' == 'b',j=1,再'a' == 'a',j=2 |
4 | a | 3 | 3 | 同上逻辑,j=3 |
所以next = [0, 0, 1, 2, 3]
第四步:KMP匹配过程
j = 0; // needle的索引
for (int i = 0; i < hLen; i++) {
// 当字符不匹配时,根据next数组回退
while (j > 0 && haystack[i] != needle[j]) {
j = next[j - 1];
}
// 如果字符匹配,移动needle的指针
if (haystack[i] == needle[j]) {
j++;
}
// 如果needle已经完全匹配,返回匹配起始位置
if (j == nLen) {
free(next);
return i - nLen + 1;
}
}
示例1:haystack = "sadbutsad", needle = "sad"
首先构建的next数组为[0, 0, 0](因为"sad"中没有重复的前后缀)
i | haystack[i] | j | needle[j] | 匹配情况 | 说明 |
---|---|---|---|---|---|
0 | s | 0 | s | 匹配 | j++ → 1 |
1 | a | 1 | a | 匹配 | j++ → 2 |
2 | d | 2 | d | 匹配 | j++ → 3 |
- | - | 3 | - | - | j==nLen,匹配成功,返回 2-3+1=0 |
所以返回索引0。
示例2:haystack = "leetcode", needle = "leeto"
构建的next数组为[0, 0, 0, 0, 0](因为"leeto"中没有重复前后缀)
i | haystack[i] | j | needle[j] | 匹配情况 | 说明 |
---|---|---|---|---|---|
0 | l | 0 | l | 匹配 | j++ → 1 |
1 | e | 1 | e | 匹配 | j++ → 2 |
2 | e | 2 | e | 匹配 | j++ → 3 |
3 | t | 3 | t | 匹配 | j++ → 4 |
4 | c | 4 | o | 不匹配 | j回退到next[3]=0,仍不匹配,j保持0 |
5 | o | 0 | l | 不匹配 | j保持0 |
6 | d | 0 | l | 不匹配 | j保持0 |
7 | e | 0 | l | 不匹配 | j保持0 |
整个过程j从未达到nLen=5,所以返回-1。
KMP算法的优势
相比暴力匹配算法,KMP算法的优势在于:
1.时间复杂度从O(n*m)降低到O(n+m)
2.当发生不匹配时,主串的指针不会回溯,避免了重复比较
3.利用已匹配的信息,通过next数组决定模式串应该移动多少位
这就是KMP算法解决字符串匹配问题的完整过程。
这个 KMP算法
我至今不理解!(20250816)
加载评论中...