误解题目引发的血案(KMP算法next数组的扩展应用)


之前LeetCode上刷到一道题,题目很简单,但是由于但是误解了题意,所以做题的时候大费周折。。为了不辜负误解题意所作出的努力,所以决定把此过程记录下来。

题目原文

对于字符串 S 和 T,只有在 S = T + … + T(T 与自身连接 1 次或多次)时,我们才认定 “T 能除尽 S”。

返回最长字符串 X,要求满足 X 能除尽 str1 且 X 能除尽 str2。

示例 1:

输入:str1 = “ABCABC”, str2 = “ABC”
输出:“ABC”

示例 2:

输入:str1 = “ABABAB”, str2 = “ABAB”
输出:“AB”

示例 3:

输入:str1 = “LEET”, str2 = “CODE”
输出:""

提示:

1 <= str1.length <= 1000
1 <= str2.length <= 1000
str1[i] 和 str2[i] 为大写英文字母

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/greatest-common-divisor-of-strings

解题思路

​ 求解字符串的最大公约串,由题意得,若存在这样的X,满足 X 能除尽 str1 且 X 能除尽 str2,则str1和str2都可看做是由若干个X组成的字符串,既然这样,m*X+n*X一定等于n*X+m*X,所以可以通过str1 + str2 是否等于 str2 + str1 来判断是否存在最大公约数。

​ 若存在最大公约数,题目给出的函数名为gcdOfStrings,回想起被数论支配的恐惧,看到gcd自然就想到了欧几里得算法。

代码

class Solution {
public:
    int gcd(int a, int b)
    {
        if (a < b)
            std::swap(a, b);
        return b == 0? a: gcd(b, a % b);
    }
    string gcdOfStrings(string str1, string str2) {
        if (str1 + str2 != str2 + str1) return "";
        return str1.substr(0, gcd(str1.length(), str2.length()));
    }
};

这个题目还可以折腾一下(榨取剩余价值)

​ 上面的题目求的是一个最长的X,如果我们把题目要求改成最小的X呢(微笑脸)

​ 当时我就是这样误解了题目,看到题目给出的难度为简单,我一度在怀疑自己到底是有多弱。。。

思路

​ 如果改成求最小长度的X,我们在获取到了最大公约串后,还需看最大公约串是否由重复的子串构成。

例如 ABABABAB 和 ABAB,原题的输出应该是ABAB,但更改要求后应该输出AB。

所以我们必须还要判断最大公约串是否可由重复子字符串组成。(好像刚刚说过?)

首先一个很简单的思路

​ 枚举子字符串的长度lenSub < len(len为原字符串长度),将原字符串分成多个子字符串,每个子字符串长度为lenSub(由此可见,lenSub整除len),再判断这些子字符串是否全部相等,若全部相等,则返回True,如果对于所有lenSub均不满足该条件,则返回False。时间复杂度为O(len*v(len)),其中v(len)为len的因数个数(因为我们只需要对整除len的lenSub进行进一步判断)。

​ 但是既然把这道题写成了博客,用枚举的方法,说得过去吗?

使用KMP中的next数组求解

​ 关于什么是KMP算法以及什么是next数组,我在《算法 - KMP》中有详细讨论。

​ 如何用next算法判断是否有重复的子串?

​ 根据next数组的性质,对于字符串s,如果j满足,0<=j<=n-1,且s(0,j) = s(n-1-j,n-1),令k=n-1-j,若k整除n,不妨设n=mk,则s(0,(m-1)k - 1) = s(k,mk - 1),即s(0,k-1) = s(k,2k-1) = …… = s((m-1)k - 1,mk - 1),即s满足题设条件。故要判断s是否为重复子串组成,只需找到满足上述条件的j,且k整除n,即说明s满足条件,否则不满足。

​ 利用已算出的next(n-1),令k=n-1-next(n-1),由c可知,若k整除n,且k < n,则s满足条件,否则不满足。上述算法的复杂度可证明为O(n)。

​ 下面我们用人类看得懂的方法来描述一下这个过程:

​ 假设一个字符串为abcabcabcabc,我们判断是不是由重复段构成。可知next[11] = 8:

在这里插入图片描述

有了上面这张图,那一段杂乱的推导就变得好理解了。那么我们上代码。

代码

public class Solution {
    public boolean repeatedSubstringPattern(String s) {
        int l = s.length();
        int[] next = new int[l];
        next[0] = -1;
        int i, j = -1;
        for (i = 1; i < l; i++) {
            while (j >= 0 && s.charAt(i) != s.charAt(j + 1)) {
                j = next[j];
            }
            if (s.charAt(i) == s.charAt(j + 1)) {
                j++;
            }
            next[i] = j;
        }
        int lenSub = l - 1 - next[l - 1];
        return lenSub != l && l % lenSub ==0;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值