代码随想录第九天 您所在的位置:网站首页 字符串指针的长度怎么定义 代码随想录第九天

代码随想录第九天

2023-06-02 23:09| 来源: 网络整理| 查看: 265

LeetCode28.实现strStr()

题目链接:28. 找出字符串中第一个匹配项的下标 - 力扣(LeetCode)

思路:

KMP有什么用:

KMP主要应用在字符串匹配上。

KMP的主要思想是当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。

所以如何记录已经匹配的文本内容,是KMP的重点,也是next数组肩负的重任.

什么是前缀表:

next数组就是一个前缀表(prefix table)。

前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。

前缀表如何记录:

首先要知道前缀表的任务是当前位置匹配失败,找到之前已经匹配上的位置,再重新匹配,此也意味着在某个字符失配时,前缀表会告诉你下一步匹配中,模式串应该跳到哪个位置。

那么什么是前缀表:记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。

前缀表具有告诉我们当前位置匹配失败,跳到之前已经匹配过的地方的能力。

最长公共前后缀:(最长相等前后缀)

字符串的前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串。

后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。

因为前缀表要求的就是相同前后缀的长度。

如何计算前缀表:

将每个字串从前开始,从后开始,依次匹配,相等next数组的值就+1.

可以看出模式串与前缀表对应位置的数字表示的就是:下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。

找到的不匹配的位置, 那么此时我们要看它的前一个字符的前缀表的数值是多少。

为什么要前一个字符的前缀表的数值呢,因为要找前面字符串的最长相同的前缀和后缀。

所以要看前一位的 前缀表的数值。

前缀表与next数组的关系:

next数组就可以是前缀表,但是很多实现都是把前缀表统一减一(右移一位,初始位置为-1)之后作为next数组。(这样做的原因是:这并不涉及到KMP的原理,而是具体实现,next数组既可以就是前缀表,也可以是前缀表统一减一(右移一位,初始位置为-1)。)

时间复杂度分析:

其中n为文本串长度,m为模式串长度,因为在匹配的过程中,根据前缀表不断调整匹配的位置,可以看出匹配的过程是O(n),之前还要单独生成next数组,时间复杂度是O(m)。所以整个KMP算法的时间复杂度是O(n+m)的。

暴力的解法显而易见是O(n × m),所以KMP在字符串匹配中极大地提高了搜索的效率。

class Solution { public: void getNext(int* next,const string& s) { int j = -1; next[0] = j; for(int i = 1;i < s.size();i++) {//注意i从1开始 while(j >= 0 && s[i] != s[j+1]) {//前后缀不相同了 j = next[j];//向前回退 } if(s[i] == s[j+1]) {//找到相同的前后缀 j++; } next[i] = j;//将j(前缀的长度)赐给next[i] } } int strStr(string haystack, string needle) { if(needle.size() == 0) return 0; int next[needle.size()]; getNext(next,needle); int j = -1;//因为next数组里记录的起始值为-1 for(int i = 0;i < haystack.size();i++) {//注意i从0开始 while(j >= 0 && haystack[i] != needle[j + 1]) {//不匹配 j = next[j];//j寻找之前匹配的位置 } if(haystack[i] == needle[j + 1]) {//匹配,j和i同时向后移动 j++;//i增加在for钟 } if(j == needle.size() - 1) {//文本串s里出现了字符串t return (i - needle.size() + 1); } } return -1; } };

备注:周末再重温一遍,弄懂了KMP算法的机制和前后缀还有next数组的运作,但还不会写代码。

先写一遍题解记忆.

LeetCode 459.重复的子字符串 

题目链接:459. 重复的子字符串 - 力扣(LeetCode)

思路:

1.暴力解法:双重for循环匹配

2.移动匹配:

当一个字符串内部由重复字符串组成时,就是由前后相同的子串组成。

那么既然前面有相同的子串,后面有相同的子串,用 s + s,这样组成的字符串中,后面的子串做前串,前后的子串做后串,就一定还能组成一个s。

所以判断字符串s是否由重复子串组成,只要两个s拼接在一起,里面还出现一个s的话,就说明是由重复子串组成。

当然,我们在判断 s + s 拼接的字符串里是否出现一个s的的时候,要刨除 s + s 的首字符和尾字符,这样避免在s+s中搜索出原来的s,我们要搜索的是中间拼接出来的s。

class Solution { public: bool repeatedSubstringPattern(string s) { string t = s + s; t.erase(t.begin()); t.erase(t.end() - 1); if(t.find(s) == -1)//find没找到返回-1 return false; else return true; } };

3.KMP算法:

 由重复子串组成的字符串中,最长相等前后缀不包含的子串就是最小重复子串!!! 数学推导:

假设字符串s使用多个重复子串构成(这个子串是最小重复单位),重复出现的子字符串长度是x,所以s是由n * x组成。

因为字符串s的最长相同前后缀的长度一定是不包含s本身,所以 最长相同前后缀长度必然是m * x,而且 n - m = 1,(这里如果不懂,看上面的推理)

所以如果 nx % (n - m)x = 0,就可以判定有重复出现的子字符串。

next 数组记录的就是最长相同前后缀,如果 next[len - 1] != -1,则说明字符串有最长相同的前后缀(就是字符串里的前缀子串和后缀子串相同的最长长度)。

最长相等前后缀的长度为:next[len - 1] + 1。(这里的next数组是以统一减一的方式计算的,因此需要+1。

数组长度为:len。

如果len % (len - (next[len - 1] + 1)) == 0 ,则说明数组的长度正好可以被 (数组长度-最长相等前后缀的长度) 整除 ,说明该字符串有重复的子字符串。

数组长度减去最长相同前后缀的长度相当于是第一个周期的长度,也就是一个周期的长度,如果这个周期可以被整除,就说明整个数组就是这个周期的循环。

class Solution { public: void getnext(int* next,const string& s) { next[0] = -1; int j = -1; for(int i = 1;i < s.size(); i++) { while(j >= 0 && s[i] != s[j+1]) { j = next[j]; } if(s[i] == s[j + 1]) { j++; } next[i] = j; } } bool repeatedSubstringPattern(string s) { if(s.size() == 0) { return false; } int next[s.size()]; getnext(next,s); int len = s.size(); if(next[len - 1] != -1 && len% (len - (next[len - 1] + 1)) == 0) { return true; } return false; } };



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

      专题文章
        CopyRight 2018-2019 实验室设备网 版权所有