代码随想录算法训练营第九天 | 28. 找出字符串中第一个匹配项的下标

KMP算法

 KMP算法(Knuth-Morris-Pratt算法)是一种字符串匹配算法,用于在文本串中查找子串。KMP算法利用已知信息加速匹配过程,时间复杂度为O(m+n),其中m为模式串的长度,n为文本串的长度。

基本思想:

KMP算法的基本思想是,在匹配过程中当发现不匹配时,将模式串向右移动一定的距离,而不是直接从文本串的下一个位置开始匹配。(暴力的方法就是将模式串在文本串上向后移动一位)移动的距离取决于已匹配的部分字符串的最长公共前后缀,即前缀和后缀的最长匹配长度。

注:

(1)为什么移动的距离取决于已匹配的部分字符串的最长公共前后缀,即前缀和后缀的最长匹配长度:

因为已匹配的部分字符串的最长公共前后缀,即前缀和后缀的最长匹配长度,可以用来指示模式串在匹配过程中出现不匹配的位置。如果这个最长匹配长度为0,说明当前的不匹配位置不能向右移动,只能将模式串整体向右移动一位,从下一个位置开始匹配。但如果最长匹配长度不为0,我们就可以将模式串向右移动这个长度的距离,从最长匹配长度的后一个字符开始重新匹配,从而避免了不必要的匹配

具体来说,假设我们已经匹配了文本串的前i个字符和模式串的前j个字符,但第j+1个字符和第i+1个字符不匹配,此时我们需要将模式串向右移动一定的距离才能继续匹配。这个移动的距离就是已匹配部分字符串的最长公共前后缀长度,因为这个长度告诉我们模式串的前缀和后缀是重复的,也就是说模式串前面的一部分可以直接跳过,直接匹配已匹配部分字符串的后缀的下一个字符

举例:

补:或者上图也可以这么理解:不匹配是出现在后缀a a的后面,又因为前缀a a与后缀相同,所以可以把前缀移过来与原后缀对应,移动后再将现在位置的文本串的前缀的后一位与刚刚模式串的位置比较

说白了就是把原来的前缀移到现在的后缀位置

 核心:

KMP算法的核心是求出模式串中每个前缀的最长公共前后缀长度,这可以通过构建前缀表(也称为失配函数)来实现。前缀表中的每个值表示模式串中该位置前缀的最长公共前后缀长度。

具体代码实现:(这种方法坚持的循环不变量是看前一位)

(1)初始化:

i:后缀末尾位置,先赋为1

j:不仅是前缀末尾位置,还是i(包括i)之前子串的最长相等前后缀长度,先赋为0

next数组:记录当前位置(包括当前位置)子串最长相等前后缀长度,next[0] = 0

int j = 0;
next[0] = j;
for (int i = 1; i < s.size(); i++) {
}

(2)前后缀末尾不同时

j向前回退,直到j指向位置的字母与i相同或j退回next数组开头

while (j > 0 && s[i] != s[j]) { // 前后缀不相同了
    j = next[j - 1]; // 向前回退
}

(3)前后缀末尾相同时

j的值先递增再赋给next[i]

if (s[i] == s[j]) { // 找到相同的前后缀
    j++;
}
next[i] = j;

完整代码

    void getNext(int* next, const string& s) {
        int j = 0;
        next[0] = 0;
        for(int i = 1; i < s.size(); i++) {
            while (j > 0 && s[i] != s[j]) { // j要保证大于0,因为下面有取j-1作为数组下标的操作
                j = next[j - 1]; // 注意这里,是要找前一位的对应的回退位置了
            }
            if (s[i] == s[j]) {
                j++;
            }
            next[i] = j;
        }
    }

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

视频讲解KMP理论

视频讲解KMP实现

个人对KMP算法理解

主要思路:

(1)构造next数组

(2)匹配模式串与文本串

代码实现:

class Solution {
public:
    void getNext(int* next, const string& s) {
        int j = 0;
        next[0] = 0;
        for(int i = 1; i < s.size(); i++) {
            while (j > 0 && s[i] != s[j]) {
                j = next[j - 1];
            }
            if (s[i] == s[j]) {
                j++;
            }
            next[i] = j;
        }
    }
    int strStr(string haystack, string needle) {
        if (needle.size() == 0) {
            return 0;
        }
        int next[needle.size()];
        getNext(next, needle);
        int j = 0;
        for (int i = 0; i < haystack.size(); i++) {
            while(j > 0 && haystack[i] != needle[j]) {
                j = next[j - 1];
            }
            if (haystack[i] == needle[j]) {
                j++;
            }
            if (j == needle.size() ) {
                return (i - needle.size() + 1);
            }
        }
        return -1;
    }
};
第二十二天的算法训练营主要涵盖了Leetcode题目的三道题目,分别是Leetcode 28 "Find the Index of the First Occurrence in a String",Leetcode 977 "有序数组的平方",和Leetcode 209 "长度最小的子数组"。 首先是Leetcode 28题,题目要求在给定的字符串找到第一个出现的字符的索引。思路是使用双指针来遍历字符串,一个指向字符串的开头,另一个指向字符串的结尾。通过比较两个指针所指向的字符是否相等来判断是否找到了第一个出现的字符。具体实现的代码如下: ```python def findIndex(self, s: str) -> int: left = 0 right = len(s) - 1 while left <= right: if s[left == s[right]: return left left += 1 right -= 1 return -1 ``` 接下来是Leetcode 977题,题目要求对给定的有序数组的元素进行平方,并按照非递减的顺序返回结果。这里由于数组已经是有序的,所以可以使用双指针的方法来解决问题。一个指针指向数组的开头,另一个指针指向数组的末尾。通过比较两个指针所指向的元素的绝对值的大小来确定哪个元素的平方应该放在结果数组的末尾。具体实现的代码如下: ```python def sortedSquares(self, nums: List[int]) -> List[int]: left = 0 right = len(nums) - 1 ans = [] while left <= right: if abs(nums[left]) >= abs(nums[right]): ans.append(nums[left ** 2) left += 1 else: ans.append(nums[right ** 2) right -= 1 return ans[::-1] ``` 最后是Leetcode 209题,题目要求在给定的数组找到长度最小的子数组,
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值