代码随想录 9.28 || 字符串 LeetCode 28.KMP、 459. 重复字符串

        本篇是KMP经典题目,KMP的经典思想就是:当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用记录的信息,缩短字符串的匹配时间。

        在KMP算法中,next数组负责记录上述信息,next数组中的每个元素,代表在当前下标及之前,最长相同前后缀的长度。什么是前缀?什么是后缀?

        前缀是指不包括最后一个字符的、以第一个字符开始的所有的连续子串,都可以称为该字符串的前缀;后缀是指不包括第一个字符的、以最后一个字符结尾的所有的连续子串。最长相同前后缀是指在字符串的前缀和后缀中,最长的相同的子串。通过最长相同前后缀计算得到的前缀表,具有告诉我们当前位置匹配失败,调到之前已经匹配过得地方的能力,而不需要从头开始。如何计算前缀表在此不做介绍。我们将前缀表记录在next数组中,通过next数组中的信息,指导字符串和模式串进行匹配,从而判断模式串存不存在于某字符串中。博主还在练习阶段,详细原理等熟悉之后补充。

        针对LeetCode28题,基于KMP算法的解决办法如下所示:

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;
    }
};

459.重复的子字符串

        给定一个非空的字符串s,检查是否可以通过它的一个子串多次重复构成。本题可以通过移动匹配和KMP解法完成,嵌套循环暴力解法也可,在此不多介绍。

        移动匹配,当一个字符串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) != std::string::npos) return true;
        return false;
    }
};

        移动匹配时间复杂度为O(n),空间复杂度为O(1)。

        KMP解法,假设字符串s由若干个重复的子串组成,那么其最长相等前后缀不包含的子串就是最小重复子串,这是因为,前缀是由首字符开始的连续子串,不能包含尾字符;后缀是由尾字符结尾的连续子串,不能包含首字符;如果要求最长前缀和后缀相同,就需要其首尾相同,以文本串的首字符开头,以文本串的尾字符结尾。所以对于最长前缀来说,其不能包含最后一个最小的重复字串,因为如果包括了就不能以尾字符结尾,对于最长后缀来说也一样,其不能包含第一个最小的重复字串,因为如果包括了就不能以首字符开始。所以最长相同前缀或最长相同后缀不包括的子串,就是最小重复子串。如果某一个文本串由重复子串构成,其长度一定可以整除最小重复子串的长度,所以我们借由一次KMP算法就能判断出某一个字符串,是不是由重复的子串组成的。

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;
        }
    }

    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] != 0 && len % (len - (next[len - 1])) == 0) {
            return true;
        }
        return false;
    }
};

你可能感兴趣的:(算法,数据结构)