【KMP 算法】ACM 选手图解 LeetCode 重复的子字符串

「这是我参与2022首次更文挑战的第35天,活动详情查看:2022首次更文挑战」。


大家好呀,我是帅蛋。

今天解决重复的子字符串,是 KMP 算法的变形题。

这道题比较有意思,把我卡了俩小时,后来是在健身房推胸的时候灵光一闪...

已经很久没碰到要提交两次才能 AC 的题了。

ae62f6a5c5c25aae03f0f41a61a7c58

牛皮不多吹,我们来看一下这道题。

e84804884c9026ac13faea239eea97d

LeetCode 459:重复的子字符串

题意

给定一个非空的子字符串,判断它是否可以由它的一个子串重复多次构成。

示例

输入:"abab" 输出:True

提示

  • 给定的字符串只含有小写英文字母,并且长度不超过 10000。

题目解析

官方难度评级,本题为简单,我怀疑 LeetCode 是在搞心态...

这要是道简单题,我直播...咳咳...

9a538ad5b9c1d7c27ae137c67837904

之前 KMP 算法我们说的是解决模式串在主串中的位置,重复的子字符串这道题不是求子串在主串中出现的位置,而是判断一个字符串是不是由多个重复子串构成。

官方点讲,这是周期性字符串问题,KMP 是解决这类问题的经典解法

但是虽说是 KMP,但是用到的只是求 next 的那一块,剩下的,得靠推公式...

我直接先来说下公式:

len(s) % (len(s) -  maxLen) = 0
复制代码

其中 len(s) 为字符串 s 的长度maxLen 为最长公共前后缀的长度

所以这个公式翻译一下就是:如果 s 是周期串,那【s 的长度】是【s 的长度减去最长公共前后缀的长度】的倍数,那字符串 s 就是周期串

至于为什么是这个公式,就得从盘古开天辟地开始了...

3be8bd01b3cdb1bbf775929e91d6100

有兴趣的去仔细研究下这篇文章的推理,讲的真的非常好,图文并茂,保证认真看完就懂了...

writings.sh/post/algori…

至于上面所说的最长公共前后缀就是 next。

具体如何求我在之前 KMP 的入门文章中有详细介绍:

ACM 选手带你玩转 KMP 算法!

4e166ecd5f77fc61ce47efcea7a6030

91c76734543396e27df8bbdd67bba16

next 数组是从 0 开始的,且它存的是位置,所以最长公共前后缀的长度应该是 next[len(s) - 1] + 1

但是只知道公式是不够的,这道题还是会卡住,没错,就是我...

if maxLen == 0 or s[n - 1] != s[n - 1 - maxLen]:
    return False
复制代码

or 前面那个我就不说了,maxLen == 0 即 next[len(s) - 1] == -1,找无可找,肯定不存在重复。

下面来重点说下 or 后面这个,我就是缺了这行代码。

4b6ffc34e97fe805fdb95aacd9bb65c

这是一种特殊的情况,按照我在【KMP 算法入门】文章里讲的 next 求法:

“abab” 的 next 值为 [-1,0,0,1],“abac” 的 next 值也为 [-1,0,0,1],前者是周期串,后者不是,但是后者的 next 值和前者一样,都会被算成周期串。

那怎么解决呢?就是加一个限制条件:

s[n - 1] != s[n - 1 - maxLen]
复制代码

如果是周期串,那对应位置上的字母应该是一样的,如果对应位置上的字母不一样,那就肯定不是周期串

d90c456c4dae742652a3eaef4f6c879

大家一定要注意。

代码实现

Python 代码实现

class Solution:
    def getNext(self, s: str):
        # 后缀匹配指向
        i = 0
        # 前缀匹配指向
        j = -1
        # 初始化 next 数组
        next = [-1] * len(s)
​
        # 此处 next[0] = -1,所以只需要求剩下的 len(T)-1 个即可
        while i < len(s) - 1:
            # j == -1 就是找无可找 or 匹配成功,相同前缀长度增加1
            if j == -1 or s[i] == s[j]:
                i += 1
                j += 1
                next[i] = j
            # 匹配不成功则在前面的子串中继续搜索,直至找不到(即 j== -1 的情况)
            else:
                j = next[j]
​
        return next
​
    def repeatedSubstringPattern(self, s: str) -> bool:
​
        if len(s) == 0:
            return False
​
        next = self.getNext(s)
        n = len(s)
        # 最长公共前后缀
        maxLen = next[n - 1] + 1
​
        if maxLen == 0 or s[n - 1] != s[n - 1 - maxLen]:
            return False
​
        return n % (n - maxLen) == 0
复制代码

Java 代码实现

class Solution {
    
    public int[] getNext(String s){
        int i = 0;
        int j = -1;
        int n = s.length();
        
        int[] next = new int[n];
        next[0] = -1;
​
        while(i < n - 1){
            if(j == -1 || s.charAt(i) == s.charAt(j)){
                i += 1;
                j += 1;
                next[i] = j;
            }else{
                j = next[j];
            }
        }
        return next;
    }
​
​
    public boolean repeatedSubstringPattern(String s) {
        int n = s.length();
​
        if (n == 0){
            return false;
        }
​
        int[] next = getNext(s);
        int maxLen = next[n - 1] + 1;
​
        if(maxLen == 0 || s.charAt(n - 1) != s.charAt(n - 1 - maxLen)){
            return false;
        }
​
        return n % (n - maxLen) == 0;
    }
}
复制代码

图解重复的子字符串到这就结束辣,呃,以后碰到这种类型的题记得用 KMP 就好了。

如果能把公式以及为何公式要这么推搞懂更好。

如果搞不懂问题也不大,本身只是为了让你练习一下 KMP 的用法,不要被困住

大家加油,记得帮我点赞 呀!

0a9789ae8a88942d6c0dc4b71ade963

我是帅蛋,我们下次见啦~

おすすめ

転載: juejin.im/post/7069189334787686437