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

40 阅读3分钟

. 找出字符串中第一个匹配项的下标 给你两个字符串 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 。

题目分析

本题要求在一个字符串(haystack)中找到另一个字符串(needle)的第一个匹配项的下标,若不存在则返回 -1。核心是实现字符串的匹配逻辑,需要考虑边界条件(如 needle 长度大于 haystack 时直接返回 -1)和逐字符比对的效率。

代码解析

你提供的代码是暴力匹配法(BF 算法),逻辑清晰且容易理解,以下是逐行解析:

cpp

运行

class Solution {
public:
    int strStr(string s, string p) {  // s对应haystack,p对应needle
        int n = s.size(), m = p.size();  // n是主串长度,m是模式串长度
        
        // 遍历主串的起始位置,i的上限是n-m(否则剩余字符不足匹配模式串)
        for(int i = 0; i <= n - m; i++){
            int j = i, k = 0;  // j:主串当前比对位置,k:模式串当前比对位置
            
            // 逐字符比对,直到模式串匹配完 或 字符不相等
            while(k < m and s[j] == p[k]){
                j++;  // 主串指针后移
                k++;  // 模式串指针后移
            }
            
            // 若模式串全部匹配完成,说明找到第一个匹配项,返回起始下标i
            if(k == m) return i;
        }
        
        // 遍历完所有可能位置都未匹配,返回-1
        return -1;
    }
};

关键要点

  1. 循环上限i <= n - m 是核心优化,避免无效遍历(比如主串只剩 2 个字符,模式串有 3 个,无需继续)。
  2. 逐字符比对:内层 while 循环逐个比对主串和模式串的字符,一旦不匹配则跳出,尝试下一个起始位置。
  3. 匹配成功判定:当 k == m 时,说明模式串的所有字符都匹配完成,直接返回当前起始下标 i(保证是第一个匹配项)。

示例验证

  • 示例 1haystack = "sadbutsad", needle = "sad"

    • n=9, m=3i 从 0 开始:

      • i=0 时,j=0, k=0,比对 s[0]='s' == p[0]='s' → j=1, k=1
      • s[1]='a' == p[1]='a' → j=2, k=2
      • s[2]='d' == p[2]='d' → j=3, k=3
      • k==m,返回 0,符合预期。
  • 示例 2haystack = "leetcode", needle = "leeto"

    • n=8, m=5i 只能遍历到 8-5=3

      • i=0 时,比对到 s[4]='c' vs p[4]='o',不匹配;
      • 后续 i=1/2/3 均无匹配,最终返回 -1,符合预期。

复杂度分析

  • 时间复杂度:最坏情况下(如主串是 aaaaa...b,模式串是 aaaaab),时间复杂度为 O(n×m),其中 n 是主串长度,m 是模式串长度。
  • 空间复杂度:O(1),仅使用了常数个临时变量。

优化思路(KMP 算法)

如果想优化时间复杂度到 O(n+m),可以使用 KMP 算法(核心是利用模式串的前缀函数减少重复比对)。以下是 KMP 实现版本:

cpp

运行

class Solution {
public:
    int strStr(string s, string p) {
        int n = s.size(), m = p.size();
        if (m == 0) return 0;
        
        // 构建前缀函数(next数组)
        vector<int> next(m, 0);
        for (int i = 1, j = 0; i < m; i++) {
            while (j > 0 && p[i] != p[j]) j = next[j - 1];
            if (p[i] == p[j]) j++;
            next[i] = j;
        }
        
        // KMP匹配
        for (int i = 0, j = 0; i < n; i++) {
            while (j > 0 && s[i] != p[j]) j = next[j - 1];
            if (s[i] == p[j]) j++;
            if (j == m) return i - m + 1;  // 匹配完成,返回起始下标
        }
        
        return -1;
    }
};

总结

你提供的暴力匹配代码逻辑正确、易于理解,能满足题目要求(即使在 1e4 长度的字符串下,暴力法也能通过测试,因为实际测试用例不会全是最坏情况)。如果追求更高效率,可以选择 KMP 算法。