KMP算法实战——力扣28.找出字符串中第一个匹配项的下标

#王者杯·14天创作挑战营·第5期#

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

在这里插入图片描述

问题描述

给定两个字符串 haystackneedle,要求在 haystack 中找出 needle 的第一个匹配项的起始下标。如果 needle 不是 haystack 的子串,则返回 -1。

示例

  • 输入:haystack = "sadbutsad", needle = "sad"
    输出:0(“sad” 在下标 0 和 6 处出现)
  • 输入:haystack = "leetcode", needle = "leeto"
    输出:-1(“leeto” 不存在于 “leetcode” 中)

解题思路:KMP算法

传统暴力解法的最坏时间复杂度为 O(m×n)(m为主串长度,n为模式串长度)。KMP算法通过预处理模式串,构建“部分匹配表”(next数组),将时间复杂度优化至 O(m+n)

核心思想

  1. 预处理模式串:构建next数组,记录模式串中前缀与后缀的最长匹配长度。
  2. 匹配过程:当主串与模式串字符不匹配时,根据next数组回退模式串指针,避免重复比较。

代码实现与注释详解

class Solution {
    public int strStr(String haystack, String needle) {
        if (needle.length() == 0) {
            return 0;
        }

        int hLength = haystack.length(), nLength = needle.length();
        int hIndex = 0, nIndex = 0; // 主串和模式串指针
        int[] next = buildNext(needle); // 构建next数组

        // KMP主循环
        while (hIndex < hLength && nIndex < nLength) {
            // 当模式串指针为-1(即模式串首个字符不匹配)或字符匹配时
            if (nIndex < 0 || haystack.charAt(hIndex) == needle.charAt(nIndex)) {
                hIndex++; // 主串指针前进
                nIndex++; // 模式串指针前进
            } else {
                // 字符不匹配,模式串指针回退到next数组对应的值
                nIndex = next[nIndex];
            }
        }

        // 若模式串指针到达末尾,说明找到匹配项
        if (nIndex == nLength) {
            return hIndex - nIndex; // 返回匹配起始位置
        }
        return -1; // 否则返回-1
    }

    /**
     * 构建next数组(部分匹配表)
     * next[j] 表示模式串的前j个字符组成的子串的最长相同前缀后缀长度
     */
    private int[] buildNext(String needle) {
        int needleLength = needle.length();
        int[] next = new int[needleLength];
        next[0] = -1; // 初始值
        int t = -1, j = 0;

        // 构建next数组
        while (j < needleLength - 1) {
            // 当t为-1(说明已经匹配到模式串开头)或字符匹配时
            if (t < 0 || needle.charAt(t) == needle.charAt(j)) {
                t++;
                j++;
                next[j] = t; // 记录当前j位置的最长前缀后缀长度
            } else {
                // 字符不匹配,t回退到next[t]
                t = next[t];
            }
        }
        return next;
    }
}

在这里插入图片描述


示例演示

示例1:

输入:haystack = "sadbutsad", needle = "sad"

  • 构建next数组:[-1, 0, 0]
  • 匹配过程:
    • i=0~2:主串与模式串匹配成功,返回起始位置 0

示例2:

输入:haystack = "leetcode", needle = "leeto"

  • 构建next数组:[-1, 0, 1, 2, 3]
  • 匹配过程:
    • 主串与模式串无法完全匹配,返回 -1

算法分析

步骤时间复杂度空间复杂度
构建next数组O(n)O(n)
KMP匹配O(m)O(1)
总体O(m+n)O(n)

注意事项

  1. next数组的含义
    next[j] 表示模式串 needle[0...j] 的最长相同前缀和后缀的长度。例如:

    • needle = "aaabaaa"next[6] = 2(前缀 “aa”,后缀 “aa”)。
  2. 边界处理

    • needle 为空字符串,直接返回 0(题目定义)。
    • haystackneedle 为 null,需单独处理(本题无需考虑)。
  3. next数组的优化
    本题采用的 next 数组是原始版本,实际工程中也可以使用改进版 next 数组,避免 t=-1 时的回退操作。


总结

KMP算法通过预处理模式串,有效避免了暴力解法的重复比较。其核心在于构建 next 数组,利用模式串的前缀信息指导回退策略。本题实现严格遵循 KMP 算法标准流程,适用于所有合法输入场景。

### C++ 中查找字符串首次出现位置的方法 在 C++ 中,`std::string` 类提供了多种成员函数来实现这一功能。其中最常用的是 `find_first_of()` 和 `find()` 函数。 #### 使用 `find_first_of` 此函数返回指定字符集合中的任意一个字符第一次出现在目标字符串的位置[^1]: ```cpp #include <iostream> #include <string> int main() { std::string str = "Hello World!"; std::string chars = "Wo"; size_t pos = str.find_first_of(chars); if (pos != std::string::npos) { std::cout << "First occurrence of any character in '" << chars << "' is at position: " << pos << std::endl; } else { std::cout << "No occurrence of any character in '" << chars << "' found." << std::endl; } } ``` 这段代码会输出:“First occurrence of any character in 'Wo' is at position: 6”。 #### 使用 `find` 如果要寻找整个子串而非单个字符,则应使用 `find()` 方法。该方法可以定位给定子串在整个字符串中首次出现的位置[^2]: ```cpp #include <iostream> #include <string> int main() { std::string haystack = "sadbutsad"; std::string needle = "sad"; size_t pos = haystack.find(needle); if (pos != std::string::npos){ std::cout << "The substring \"" << needle << "\" was found at index " << pos << "." << std::endl; } else{ std::cout << "Substring not found!" << std::endl; } return 0; } ``` 上述程序将会打印“The substring "sad" was found at index 0.”因为这是 `"sad"` 首次出现在源字符串中的索引位置[^4]。 对于更复杂的模式匹配需求,还可以考虑采用 KMP 算法等高级技术[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值