459、重复的子字符串

题目描述:

给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。

示例1:

输入: s = “abab”
输出:true
解释: 可由子串 “ab” 重复两次构成。

示例2:

输入: s = “aba”
输出: false

示例3

输入: s = “abcabcabcabc”
输出: true
解释: 可由子串 “abc” 重复四次构成。 (或子串 “abcabc” 重复两次构成。)

思路

暴力解法:
用一遍for循环遍历子字符串的终止位置,枚举出子字符串,然后和母字符串匹配,若刚好能重复构成母字符串就返回true。不能则返回false。

这里选取终止位置最大可以只取到母字符串的一半,因为子字符串要在母字符串中重复,最大长度不超过母字符串的一半。

移动匹配:
当一个字符串s:abcabc,内部由重复的子串组成,那么这个字符串的结构一定是这样的:

也就是由前后相同的子串组成。

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

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

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

kmp解法:
next数组可以得出当前字符串的最大前后缀长度,如果s是循环字符串,那么next[len]一定是它最大循环字符子串的长度,总的字符串长度一定是循环子串长度的倍数,此时只要我们只要判断len能否被len - next[len]整除即可。

原因:
下图中绿色箭头表示箭头两向的字符都是相等的(因为是最长公共前后缀)。由于s[0]=t[0],t[0]=k[0], k[0]=s[2],即s[0]=s[2],同理可得出s[1]=s[3]。证明两公共前后缀交错的那部分是最小重复子串。

代码

暴力解法:

bool repeatedSubstringPattern(string s) {
    if (s.size() == 0)return false;
    for (int i = 1; i <= s.size() / 2; i++) {
        if (i % 2 != 0)continue;
        string t = s.substr(0, i);//i代表每次截取的字符串的长度
        bool flag = true;
        for (int j = i; j < s.size(); j+=i) {
            string n = s.substr(j, i);
            if (n != t)
            {
                flag = false;//表示此次i选取的子字符串不能重复构成母字符串
                break;
            }
        }
        if (flag)return true;//表示此次i选取的子字符串能重复构成母字符串
    }
    return false;
}


移动匹配解法:

bool repeatedSubstringPattern(string s) {
    string t = s + s;
    t.erase(t.begin());
    t.erase(t.end() - 1);
    if (t.find(s) != -1)return true;
    else return false;
}

 

kmp算法:

void getNext(int* next, string& s) {
    int i, j = 0;
    next[0] = j;
    for (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] && len % (len - next[len - 1]) == 0)return true;
    return false;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值