找出字符串中第一个匹配项的下标

954 字
5 分钟
找出字符串中第一个匹配项的下标

题目链接:28. 找出字符串中第一个匹配项的下标 - 力扣(LeetCode)

  • 难度:简单
  • 标签:双指针、字符串、KMP 算法

题目描述#

Note

原题说明: 给你两个字符串 haystackneedle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1

示例#

  • 输入haystack = "sadbutsad", needle = "sad" -> 输出0
  • 输入haystack = "leetcode", needle = "leeto" -> 输出-1

方案一:朴素滑动窗口(暴力匹配)#

核心思路: 逐位遍历文本串 haystack,每次以当前字符为起点,截取一段与 needle 等长的子串进行对比。如果不匹配,起点向后移动一位。

源码实现#

class Solution {
public:
int strStr(string haystack, string needle) {
if (needle.empty()) return 0;
int n = haystack.size(), m = needle.size();
// 只需要遍历到能够容纳 needle 的最后位置
for (int i = 0; i <= n - m; ++i) {
int j = 0;
while (j < m && haystack[i + j] == needle[j]) {
j++;
}
if (j == m) return i; // 全部匹配成功
}
return -1;
}
};

复杂度分析#

  • 时间复杂度O(n×m)O(n \times m)。其中 nn 是文本串长度,mm 是模式串长度。在最坏情况下(如 aaaaaa 中找 aaaab),每次都需要匹配到最后一位才失败。
  • 空间复杂度O(1)O(1)

方案二:C++ 标准库函数#

在实际开发中,直接使用 C++ string 提供的 find 接口通常是最稳妥且高效的选择,其内部通常针对不同长度的字符串进行了高度优化。

class Solution {
public:
int strStr(string haystack, string needle) {
size_t found = haystack.find(needle);
return (found != string::npos) ? (int)found : -1;
}
};

方案三:KMP 算法(深度进阶)#

KMP (Knuth-Morris-Pratt) 算法的核心思想是:当字符串不匹配时,利用已匹配部分的信息,尽量减少模式串的回退距离,从而避免重复比较。

1. 理解 Next 数组#

next[j] 数组存储的是模式串中 needle[0...j]最长相等真前后缀长度

  • 作用:失配时,告诉你模式串指针 j 该跳到哪个位置,而文本串指针 i 保持不动

示例推导:needle = "ababaca"#

下标0123456
字符ababaca
next0012301

2. 代码实现#

#include <vector>
#include <string>
using namespace std;
class Solution {
public:
int strStr(string haystack, string needle) {
if (needle.empty()) return 0;
int n = haystack.size(), m = needle.size();
vector<int> next = buildNext(needle);
for (int i = 0, j = 0; i < n; ) {
if (haystack[i] == needle[j]) {
i++; j++;
if (j == m) return i - j; // 完全匹配成功
} else {
if (j > 0) j = next[j - 1]; // 利用 next 数组“跳跃”
else i++; // 模式串已到头,文本串后移
}
}
return -1;
}
private:
// 构建 Next 数组(最长公共前后缀)
vector<int> buildNext(const string& p) {
int m = p.size();
vector<int> next(m, 0);
for (int i = 1, len = 0; i < m; ) {
if (p[i] == p[len]) {
next[i++] = ++len;
} else if (len > 0) {
len = next[len - 1]; // 回退 len
} else {
next[i++] = 0;
}
}
return next;
}
};

复杂度分析#

  • 时间复杂度O(n+m)O(n + m)。构建 next 数组需要 O(m)O(m),主循环匹配需要 O(n)O(n)。指针 ii 始终单调不减。
  • 空间复杂度O(m)O(m)。需要额外的空间存储 next 数组。

总结#

  • 从暴力到优雅:暴力的核心是“回退文本串指针”,而 KMP 的核心是“只跳模式串指针”。
  • 工程实践:虽然 KMP 在算法面试中地位极高,但在实际处理非极其庞大的字符串时,方案一或二的性能往往足够,且代码逻辑更简单。
Tip

学习 KMP 的难点不在于代码,而在于理解“最长公共前后缀”是如何通过重用信息来消除冗余匹配的。通关了这道题,字符串匹配的大门就真正向你开启了!

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

找出字符串中第一个匹配项的下标
https://firefly-7a0.pages.dev/posts/leetcode_notes/28find-the-index-of-the-first-occurrence-in-a-string/
作者
lonelystar
发布于
2025-10-11
许可协议
CC BY-NC-SA 4.0
最后更新于 2025-10-11,距今已过 200 天

部分内容可能已过时

评论区

Profile Image of the Author
LonelyStar
Hello, I'm LonelyStar.
公告
欢迎来到我的博客!
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
119
分类
11
标签
346
总字数
226,548
运行时长
0
最后活动
0 天前

目录