力扣解题-28. 找出字符串中第一个匹配项的下标

7 阅读6分钟

力扣解题-28. 找出字符串中第一个匹配项的下标

实现 strStr() 函数。

给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。

说明:

当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。

对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与 C 语言的 strstr() 以及 Java 的 indexOf() 定义相符。

示例 1:

输入:haystack = "hello", needle = "ll"

输出:2

示例 2:

输入:haystack = "aaaaa", needle = "bba"

输出:-1

提示:

1 <= haystack.length, needle.length <= 104

haystack 和 needle 仅由小写英文字符组成

Related Topics

双指针、字符串、字符串匹配


第一次解答

解题思路

核心方法:调用Java内置indexOf方法,直接利用JDK优化后的字符串匹配逻辑,实现极简代码且性能拉满,时间复杂度接近O(n+m)(n为haystack长度,m为needle长度),是工程开发中最简洁的解法。

核心逻辑拆解

Java的String.indexOf(String str)方法底层实现了高效的字符串匹配算法(并非简单暴力匹配,JDK会根据字符串长度选择不同优化策略),其核心行为完全贴合本题要求:

  1. needle为空字符串,返回0;
  2. needlehaystack中存在,返回第一个匹配的起始下标;
  3. 若不存在,返回-1。

该解法直接复用JDK的成熟实现,无需手动编写匹配逻辑,兼顾简洁性和性能。

性能说明
  • 时间复杂度:接近O(n+m)(JDK内置实现的优化效果),耗时0ms击败100%用户;
  • 空间复杂度:O(1)(无额外内存开销,仅调用内置方法);
  • 优势:代码行数最少,无需关注匹配细节,开发效率极高;
  • 内存表现:42.21MB击败81.40%用户,是内置方法的正常内存开销,无冗余操作。
  public int strStr(String haystack, String needle) {
         int index=haystack.indexOf(needle);
       
        return index;
    }

暴力匹配法

解题思路

核心方法:暴力匹配法(逐位比对),手动实现字符串匹配的基础逻辑,逐个遍历haystack的起始位置,与needle逐字符比对,是理解字符串匹配核心原理的基础解法。

核心逻辑拆解

暴力匹配的核心是“穷举所有可能的起始位置”:

  1. needle为空,直接返回0;
  2. 遍历haystack中所有可能的起始位置i(范围:0 ~ haystack.length() - needle.length());
  3. i开始,逐字符比对haystack[i+j]needle[j](j为needle的字符下标);
  4. 若所有字符都匹配,返回起始下标i;若某一位不匹配,跳出当前比对,尝试下一个起始位置;
  5. 遍历完所有位置仍未匹配,返回-1。
具体步骤(以示例1 haystack="hello",needle="ll"为例)
  1. needle长度=2,haystack长度=5,起始位置范围:0~3;
  2. i=0:比对h vs l → 不匹配;
  3. i=1:比对e vs l → 不匹配;
  4. i=2:比对l vs l → 匹配,继续比对l vs l → 全部匹配,返回2。
性能说明
  • 时间复杂度:O(n×m)(最坏情况需遍历n-m+1个起始位置,每个位置比对m个字符);
  • 空间复杂度:O(1)(仅使用几个指针变量);
  • 优势:逻辑直观,无需依赖内置方法,是字符串匹配的入门级实现;
  • 劣势:在最坏场景(如haystack="aaaaa...a",needle="aaaaab")下性能较差,无法适配10⁴级别的大数据量。
public int strStr(String haystack, String needle) {
    // 处理空模式串
    if (needle.isEmpty()) return 0;
    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算法(最优字符串匹配算法),通过预处理needle生成next数组(部分匹配表),避免暴力匹配中的重复比对,将时间复杂度优化至O(n+m),是字符串匹配的经典最优解。

核心原理铺垫

KMP算法的核心是“利用已匹配的部分信息,跳过不必要的比对”:

  1. next数组:记录needle中每个位置j的“最长相等前后缀长度”,用于匹配失败时快速回退needle的指针,而非回退haystack的指针;
  2. 匹配过程:
    • i遍历haystackj遍历needle
    • 字符匹配时,ij同时前进;
    • 字符不匹配时,j根据next数组回退(j = next[j-1]),i不回退;
    • j等于needle长度时,匹配成功,返回i-j
next数组生成逻辑

next[i]表示needle[0..i]的最长相等前后缀长度:

  1. 初始化j=0(前缀指针),next[0]=0
  2. 遍历i=1needle.length()-1
    • needle[i] != needle[j]j>0j回退到next[j-1]
    • needle[i] == needle[j]j++
    • next[i] = j
性能优势
  • 时间复杂度:O(n+m)(预处理next数组O(m),匹配过程O(n)),无重复比对,适配10⁴级别的大数据量;
  • 空间复杂度:O(m)(存储next数组);
  • 核心价值:在haystack很长、needle有重复前缀的场景下,性能远超暴力匹配,是面试高频考点。
public int strStr(String haystack, String needle) {
    if (needle.isEmpty()) return 0; // 模式串为空,直接返回 0
    int[] next = getNext(needle); // 先生成 next 数组
    int i = 0; // 文本串指针
    int j = 0; // 模式串指针
    while (i < haystack.length()) { // 遍历文本串
        // 字符相等,两个指针都前进
        if (haystack.charAt(i) == needle.charAt(j)) {
            i++;
            j++;
        }
        // 匹配成功:j 等于模式串长度,返回起始下标
        if (j == needle.length()) {
            return i - j;
        }
        // 字符不相等的情况
        else if (i < haystack.length() && haystack.charAt(i) != needle.charAt(j)) {
            // j≠0:利用 next 数组回退 j,不回退 i(KMP 优化点)
            if (j != 0) {
                j = next[j - 1];
            }
            // j=0:没有可回退的,只能前进文本串指针 i
            else {
                i++;
            }
        }
    }
    return -1; // 遍历完文本串都没匹配到,返回 -1
}

public int [] getNext(String needle){
    int next[]= new int[needle.length()];
    int j = 0;
    for(int i=1;i<needle.length();i++){
        while (j>0 && needle.charAt(i) != needle.charAt(j)){
            j = next[j-1];
        }
        if(needle.charAt(i) == needle.charAt(j)){
            j++;
        }
        next[i] = j;
    }
    return next;
}

总结

  1. 内置方法法:工程开发首选,代码极简且性能优异,依赖JDK优化实现;
  2. 暴力匹配法:逻辑直观,适合理解字符串匹配核心原理,但最坏时间复杂度O(n×m),大数据量下性能差;
  3. KMP算法:字符串匹配的最优解,时间复杂度O(n+m),通过next数组避免重复比对,是面试核心考点;
  4. 关键技巧:
    • 处理边界条件(空模式串返回0);
    • KMP算法的核心是next数组的生成,理解“最长相等前后缀”是掌握KMP的关键;
    • 暴力匹配可通过“剪枝”(遍历到n-m为止)减少无效操作。