(力扣28)找出字符串中第一个匹配的下标(next数组,nextval数组)

题目

来源:力扣28题
给你两个字符串 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.朴素模式匹配

第1种就是纯暴力解法了(我没写相关的代码),整体就是从主串的第一个字符开始与模式串一一对比,字符相同,指针向后移动。不相同,主串与子串都回退,然后从下一个位置重新开始比较。

在这里插入图片描述

2.KMP-next数组

这是对暴力解法的一种优化,在不相同字符之前的字符我们都是匹配过了的,已经知道是什么字符了,因而主串指针每次回退都是有点浪费时间的。

在这里插入图片描述
我们可以选择不让主串指针回退,只让模式串的指针回退,那么问题来了,模式串的指针回退到哪里才是正确位置呢?

我们先手算模拟一下,如图。假设我们在主串下标为5的位置匹配失败,现寻找模式串指针的位置:从主串下标为1的位置出发,人工对比字符是否相同,不相同模式串往后挪,直到部分子串完全相同时停下,此时 j 指针指向的下标就是模式串指针应该回退的位置。

在这里插入图片描述
那如何用代码描述此过程呢?
这里使用next数组,next[0] 表示模式串在第一个位置匹配失败时,下标 j 应该回退的位置,next[1]表示模式串在第2个位置匹配失败时应该回退的位置,以此类推,next[2]….

关于next[0]和next[1],直接写-1和0就行,但如果你的数组下标不是从0开始的,是从1开始的话,就写0和1。

现在,就是具体代码应该怎么写了。说明:模式串叫 s,下标从0开始。

  1. 关于next[i]的数值,我们需要看next[i-1]的位置,为了方便记忆,我们记 t = i-1(因为 t 的指向可能会变,但我们始终比较的是模式串 s 下标为 i-1 处的字符)。
  2. 比较 s[i-1] 的字符与 s[next[t]] 的字符是否相同,如果不相同,让 t 指向 next[t] 的位置,再次比较 s[i-1] 与 s[next[t]] 处的字符是否相同,不相同 t 指针再退,如此,直到相同或者 t=0时停下,此时如果 t != 0, next[i] = next[t] + 1, 如果 t = 0,next[i] = 0。

写几个例子,如下。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.nextval数组

关于 next 数组还可以优化,如下。当我们在主串下标为2的位置匹配失败时,可以知道 2 这个位置绝对不是字符 a,但是按照 next 数组,我们的 j 下标又重新指向了模式串下标为 0 的位置,这个位置也是字符 a ,可以不用比较了,让 j 值等于该位置匹配失败时的next数组下标即可。

优化一下就是,如果在 next[i] 处匹配失败,j 指针回退位置的字符依旧是 s[next[i]],可以让 next[i] 位置的值等于 next[next[i]]的值。这就是 nextval数组,在 next 数组的基础之上进一步优化。nextval[0] 直接等于-1。

在这里插入图片描述
写个例子,如下。

在这里插入图片描述

代码

int strStr(char* haystack, char* needle) {
    // 想到了kmp算法,那就练一下吧
    int len1 = strlen(haystack);
    int len2 = strlen(needle);
    if (len2 > len1)
        return -1;

    // next数组
    int next[len2+1];//我这里next的数组长度+1是为了防止溢出,因为我的next[0]和next[1]是直接填的,next[1]可能不存在
    next[0] = -1;
    next[1] = 0;
    for (int i = 2; i < len2; i++) {
        int t = i - 1;
        while (t!=0 && needle[i-1]!=needle[next[t]]) {
            //注意这里是与前一个位置比较,区分一下i和t
            t = next[t];
        }
        if (t == 0)
            next[i] = 0;
        else
            next[i] = next[t] + 1;
    }

    // 求nextval数组
    int nextval[len2];
    nextval[0] = -1;
    for (int i = 1; i < len2; i++) {
        if (needle[next[i]] == needle[i])
            nextval[i] = nextval[next[i]];
        else
            nextval[i] = next[i];
    }

    // 匹配
    int i = 0, j = 0;
    while (i < len1 && j < len2) {
        if (j == -1 || haystack[i] == needle[j]) {
            i++;
            j++;
        } else
            j = nextval[j];
    }
    
    if (j == len2)
        return i-len2;
    else
        return -1;
} 

总结

弯弯绕绕,慢慢捋一捋,我还是最讨厌这种算法了,每次要捋好久,一边手算,一边想着代码咋整,😭😭

如若有错,欢迎各位指出。

2024.7.22

  • 6
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱写代码的小鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值