每日一题——重复的子字符串

文章介绍了如何使用KMP算法来判断一个字符串是否能由其重复的子串构成。方法一是通过构建新串并进行KMP匹配,方法二是直接分析Next数组判断最长相等前后缀。两种方法均涉及到字符串处理和Next数组的概念。
摘要由CSDN通过智能技术生成

每日一题

重复的子字符串

题目链接

在这里插入图片描述

注:本题的题解基本建立在KMP算法之上,对KMP算法不太了解的小伙伴可以参考这篇文章KMP算法及其改进图文详解

方法一:移动匹配

  • 我们先来看几个可以由一个字串重复多次构成的主字符串:“aaa”,“ababab”,“abcabc”

  • 可以发现满足条件主串的两个规律:

    • 一定可以在其前半部分和后半部分找到相等的子串,如上面三个例子的子串“a”,子串“ab”,子串“abc”
    • 交换前半部分和后半部分都相等的子串,主串仍然不变。
  • 由此可以得到,如果我们将主串复制一份,再将这两个相同的串拼到一起,并去头去尾(防止查找的过程中找到原来的串和复制后的串),如果能在新串中找到和主串相等的子串,那就说明这个主串就可以由它的一个子串重复构成

    • 例如字符串abcabc

      在这里插入图片描述

实现代码
//获得Next数组
void GetNext(char *s,int *Next)
{
    int len = strlen(s);
    Next[0] = -1;
    int i = 0;
    int k = -1;
    while(i < len - 1)
    {
        if(k == -1 || s[i] == s[k])
        {
            k++;
            i++;
            if(s[i] == s[k])
                Next[i] = Next[k];
            else
                Next[i] = k;
        }
        else
            k = Next[k];
    }
}
//判断是否可以在字符串s1中找到串s2
bool KMP(char *s1, char *s2, int *Next)     //s1为主串,s2为从串
{
    int len1 = strlen(s1);
    int len2 = strlen(s2);
    int i = 0, j = 0;
    while(i < len1 && j < len2)
    {
        if(s1[i] == s2[j])
        {
            i++;
            j++;
        }
        else if(j != 0)
        {
            j = Next[j];
            if(j == -1)
                j = 0;
        }
        else
            i++;
            
    }
    if(j >= len2)
        return true;
    else
        return false;
}
bool repeatedSubstringPattern(char * s){
    int i,j;
    int len = strlen(s);
    char *str = (char *)malloc(sizeof(char) * 2 * len + 1);
    int *Next = (int *)malloc(sizeof(int) * len);
    GetNext(s,Next);	//获得Next数组
    //将串s和其复制串合并到str中
    for(i = 0; i < len; i++)
        str[i] = s[i];
    j = i;
    for(i = 0; i < len; i++)
        str[j + i] = s[i];
    str[2 * len] = '\0';
    //去尾
    str[2 * len - 1] = '\0';
    return KMP(str + 1,s,Next);		//str+1相当于去头
}

方法二:KMP

  • 我们知道,KMP算法中,有Next数组保存了每个字符之前的子串的最长相等前后缀长度,那么最长相等前后缀长度和最小的重复子串有什么关系呢?
  • 直接下结论:
    • 如果一个字符串是由重复的子串构成,那么最长相等前后缀不包含的子串就是最小的重复子串
    • 如果一个字符串没有相等前后缀,那么这个字符串一定不能由重复子串构成
      • 由于本篇Next数组记录的是每个字符之前的子串的最长相等前后缀长度,因此为了判断该字符串是否具有相等的前后缀,需要在字符串的末尾再添加一个字符(随便什么字符都可以),这样,如果字符串没有相等的前后缀,那么Next[len - 1] = 0(len为Next数组大小)
    • 字符串的最大相等前后缀长度为Next[len - 1],由第一个结论可得,最小重复子串为len - next[len - 1],若len % (len - next[len - 1]) == 0 ,就说明字符串长度可以整除最小重复子串的长度,即可说明该字符串可以由重复子串构成。
实现代码
//求Next数组
void GetNext(char *s,int *Next)
{
    int len = strlen(s);
    Next[0] = -1;
    int i = 0;
    int k = -1;
    while(i < len - 1)
    {
        if(k == -1 || s[i] == s[k])
        {
            k++;
            i++;
            Next[i] = k;
        }
        else
            k = Next[k];
    }
}

bool repeatSubstring(char* str ) {
    int len = strlen(str);
    
    //创建一个比原字符串大两个字符(一个随机字符,和空字符)的字符数组
    char* s = (char*)malloc(sizeof(char) * (len + 2));		
    
    //将原字符串拷贝到新字符串中,并添加随机字符和空字符
    strcpy(s,str);
    s[len] = 'a';
    s[len + 1] = '\0';
    
    //申请Next数组的内存
    int *Next = (int *)malloc(sizeof(int) * (len + 1));
    
    //得到Next数组
    GetNext(s,Next);
    
    //如果该字符串由相等前后缀,并且字符串长度可以整除最小重复子串,那么就说明这个字符串可以由重复子串构成
    //这里的len之所以不要减一,是因为Next的大小为len+1,我们要得到的就是Next的最后一个值,即Next[len]
    if(Next[len] != 0 && len % (len - Next[len]) == 0)
        return true;
    
    return false;
}
  • 8
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Forward♞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值