大家好,我是你们的算法小伙伴。今天我们来练习一道字符串匹配 的经典简单题 ------LeetCode 28. 找出字符串中第一个匹配项的下标。这道题考察字符串子串匹配,是面试中常见的基础题,也是学习 KMP 算法的入门题。
题目描述
给你两个字符串 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 <= 10^4haystack和needle仅由小写英文字符组成
解题思路
方法一:暴力匹配
- 遍历
haystack中所有可能的起始位置i(范围[0, len(haystack)-len(needle)])。 - 对于每个
i,逐个字符比较haystack[i:i+m]和needle(m是needle长度)。 - 若完全匹配,直接返回
i;若遍历完都不匹配,返回-1。
方法二:KMP 算法
利用 "部分匹配表(LPS 数组)"避免重复比较,时间复杂度优化到 O (n+m),适合处理大数据量。
方法三:Java 内置方法
直接使用 String.indexOf() 方法,底层已做优化,代码最简洁。
代码实现
方法一:暴力匹配
class Solution {
public int strStr(String haystack, String needle) {
int n = haystack.length();
int m = needle.length();
// 遍历所有可能的起始位置
for (int i = 0; i <= n - m; i++) {
int j = 0;
// 逐个字符比较
while (j < m && haystack.charAt(i + j) == needle.charAt(j)) {
j++;
}
// 完全匹配,返回起始下标
if (j == m) {
return i;
}
}
// 未找到匹配
return -1;
}
}
方法二:KMP 算法
class Solution {
public int strStr(String haystack, String needle) {
int n = haystack.length();
int m = needle.length();
if (m == 0) return 0;
// 1. 构建 LPS 数组(最长前缀后缀数组)
int[] lps = new int[m];
int len = 0; // 最长公共前后缀长度
int i = 1;
while (i < m) {
if (needle.charAt(i) == needle.charAt(len)) {
len++;
lps[i] = len;
i++;
} else {
if (len != 0) {
len = lps[len - 1];
} else {
lps[i] = 0;
i++;
}
}
}
// 2. KMP 匹配
i = 0; // haystack 指针
int j = 0; // needle 指针
while (i < n) {
if (haystack.charAt(i) == needle.charAt(j)) {
i++;
j++;
}
if (j == m) {
return i - j; // 找到匹配,返回起始下标
} else if (i < n && haystack.charAt(i) != needle.charAt(j)) {
if (j != 0) {
j = lps[j - 1]; // 回退到最长公共前后缀位置
} else {
i++;
}
}
}
return -1;
}
}
方法三:内置方法
class Solution {
public int strStr(String haystack, String needle) {
return haystack.indexOf(needle);
}
}
代码详解(暴力匹配示例 1 模拟)
示例 1:haystack = "sadbutsad", needle = "sad"
n = 9,m = 3,遍历i从 0 到 6:i=0:比较haystack[0] = 's'、haystack[1] = 'a'、haystack[2] = 'd',与needle完全匹配 → 返回0。
复杂度分析
| 方法 | 时间复杂度 | 空间复杂度 | 优点 |
|---|---|---|---|
| 暴力匹配 | O(n*m) | O(1) | 代码简单,易理解 |
| KMP 算法 | O(n+m) | O(m) | 高效,适合大数据量 |
| 内置方法 | O (n)(底层优化) | O(1) | 代码最简洁,面试推荐 |
总结
- 基础版:暴力匹配适合理解,代码简单,适合数据量小的场景。
- 进阶版:KMP 算法是字符串匹配的经典算法,核心是利用 LPS 数组避免重复比较,时间复杂度更优。
- 简洁版 :直接调用
indexOf(),在面试中是最快的写法,且底层已做优化。
这道题是字符串处理的入门题,掌握暴力匹配和 KMP 算法,能为后续学习更复杂的字符串算法打下基础。
今天的每日算法练习就到这里,我们明天再见!👋