找出字符串中第一个匹配项的下标
954 字
5 分钟
找出字符串中第一个匹配项的下标
题目链接:28. 找出字符串中第一个匹配项的下标 - 力扣(LeetCode)
- 难度:简单
- 标签:双指针、字符串、KMP 算法
题目描述
Note
原题说明:
给你两个字符串 haystack 和 needle ,请你在 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; }};复杂度分析
- 时间复杂度:。其中 是文本串长度, 是模式串长度。在最坏情况下(如
aaaaaa中找aaaab),每次都需要匹配到最后一位才失败。 - 空间复杂度:。
方案二: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"
| 下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
| 字符 | a | b | a | b | a | c | a |
next | 0 | 0 | 1 | 2 | 3 | 0 | 1 |
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; }};复杂度分析
- 时间复杂度:。构建
next数组需要 ,主循环匹配需要 。指针 始终单调不减。 - 空间复杂度:。需要额外的空间存储
next数组。
总结
- 从暴力到优雅:暴力的核心是“回退文本串指针”,而 KMP 的核心是“只跳模式串指针”。
- 工程实践:虽然 KMP 在算法面试中地位极高,但在实际处理非极其庞大的字符串时,方案一或二的性能往往足够,且代码逻辑更简单。
Tip
学习 KMP 的难点不在于代码,而在于理解“最长公共前后缀”是如何通过重用信息来消除冗余匹配的。通关了这道题,字符串匹配的大门就真正向你开启了!
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!
最后更新于 2025-10-11,距今已过 200 天
部分内容可能已过时
相关文章 智能推荐
1
最后一个单词的长度
算法 力扣第58题:最后一个单词的长度。从反向遍历的直观思想到双指针的精准定位,掌握字符串处理的边界细节。
2
回文数
算法 力扣第9题:回文数的判断。从暴力取余到“反转一半”的思路演进,以及字符串双指针解法。
3
有效的括号
算法 力扣第20题:有效的括号。从“回文”误区到栈(Stack)的经典应用,深入理解括号匹配的 LIFO 逻辑。
4
删除有序数组中的重复项
算法 力扣第26题:原地删除有序数组中的重复项。从基础的标记替换,到快慢指针(Two Pointers)的高效演进。
5
合并两个有序数组
算法 力扣第88题:合并两个有序数组。从正向合并的繁琐到逆向双指针的极简之美,实现 $O(m+n)$ 的高效原地合并。
随机文章 随机推荐