L - Substrings -KMP-substr-find

本文介绍了一种用于查找多个字符串中最长公共子串的有效算法。通过从最短字符串出发,逐步检查所有可能的子串及其逆序是否存在于其他字符串中,最终找到最长公共子串。提供了两种实现方式,一种使用STL中的substr函数,另一种采用KMP算法进行匹配。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • L - Substrings

     HDU - 1238 

  •  

  • substr

  •  
  • (C++语言函数)

  •  编辑
  • substr是C++语言函数,主要功能是复制子字符串,要求从指定位置开始,并具有指定的长度。
  • 题意 :
  • 查找最长公共子串 ,可以从最小子串找起 ,并且可以查找逆序是否是别的串的子串。
  • 注意最后若找不到输出0。
  • STL:
  • #include<bits/stdc++.h>
    using namespace std;
    #define maxn 155
    string str[maxn],son,s1,s2;
    int len,t,n,mini,k;
    int main()
    {
        cin>>t;
        while(t--)
        {
            mini=555;
            bool flag;
            cin>>n;
            for(int i=0; i<n; i++)
            {
                cin>>str[i];
                len=str[i].size();
                if(len<mini)
                {
                    mini=len;
                    son=str[i];
                }
            }
            for(int i=mini; i>0; i--)//i是长度
            {
                flag=1;
                for(int j=0; j<mini+1-i; j++) //j是起始位置,起始位置得保证后面的字符够取得才可以
                {
                    s1=son.substr(j,i);
                    s2=s1;
                    reverse(s2.begin(),s2.end());
                    for( k=0; k<n; k++)
                        if(str[k].find(s1)==-1&&str[k].find(s2)==-1)
                            break;
                    if(k==n)
                        break;
                }
                if(k==n)
                {
                    cout<<s1.size()<<endl;
                    flag=0;
                    break;
                }
            }
            if(flag)
                cout<<0<<endl;
        }
        return 0;
    }
  • KMP:
  • #include<bits/stdc++.h>
    using namespace std;
    #define maxn 155
    string str[maxn],son,s[5];
    int len,t,n,mini,k,nxt[5][maxn];
    void getnext(int lengt,int cnt)
    {
        int i=0,j=-1;
        nxt[cnt][0]=-1;
        while(i<lengt)
        {
            if(j==-1||s[cnt][i]==s[cnt][j])
            {
                i++;
                j++;
                nxt[cnt][i]=j;
            }
            else
                j=nxt[cnt][j];
        }
    }
    int kmp(int cnt,int indx)
    {
        int i=0,j=0;
        while(i<str[cnt].size())
        {
            if(j==-1||str[cnt][i]==s[indx][j])
            {
                i++;
                j++;
            }
            else
                j=nxt[indx][j];
            if(j==s[indx].size())
                return 1;
        }
        return -1;
    }
    int main()
    {
        cin>>t;
        while(t--)
        {
            mini=555;
            bool flag;
            cin>>n;
            for(int i=0; i<n; i++)
            {
                cin>>str[i];
                len=str[i].size();
                if(len<mini)
                {
                    mini=len;
                    son=str[i];
                }
            }
            for(int i=mini; i>0; i--)//i是长度
            {
                flag=1;
                for(int j=0; j<mini+1-i; j++) //j是起始位置,起始位置得保证后面的字符够取得才可以
                {
                    s[1]=son.substr(j,i);
                    s[2]=s[1];
                    reverse(s[2].begin(),s[2].end());
                    len=s[1].size();
                    getnext(len,1);
                    getnext(len,2);
                    for( k=0; k<n; k++)
                        if(kmp(k,1)==-1&&kmp(k,2)==-1)
                            break;
                    if(k==n)
                        break;
                }
                if(k==n)
                {
                    cout<<s[1].size()<<endl;
                    flag=0;
                    break;
                }
            }
            if(flag)
                cout<<0<<endl;
        }
        return 0;
    }
    
<think>嗯,用户给了一个C++代码,需要优化它的运行时间。让我先仔细看看这段代码是做什么的。首先,代码的大致结构是这样的:输入一个字符串s,然后找出其中所有长度不超过10的子串,计算每个子串出现的总长度(子串长度乘以出现次数),然后找到总长度最大的那个,如果有多个相同的,取字典序最小的。最后输出这个子串。 现在,用户希望优化这段代码,让运行时间更短。原代码的时间复杂度可能比较高,所以需要分析哪里可以优化。 首先看主循环部分。外层循环是i从1到min(s.length(),10),也就是处理子串长度1到10的情况。内层循环j从0到s.length()-i,遍历所有起始位置,取出子串m。然后调用figure函数计算这个子串m在s中的出现次数。每次计算次数的时候,figure函数会从头开始查找,每次找到后pos加上子串长度,避免重叠。这个figure的时间复杂度是O(n),其中n是原字符串的长度。假设原字符串长度是N,子串长度最多是10,那么总的复杂度大概是O(10*N*(N/1 + N/2 + ... + N/10))),这可能会很高,特别是当N很大的时候,比如1e5的话,这样的复杂度肯定不行。 不过原题中的字符串长度可能不是特别大,但用户显然希望优化时间。所以需要找出哪里可以优化。 现在,原代码的问题可能在于,对于每个可能的子串m,都调用figure函数重新遍历整个字符串来计算出现次数。这样重复计算了很多次相同的子串。比如,对于子串"ab",可能在多个不同的j位置被截取到,但每次都要重新计算它在整个s中的出现次数,这样效率很低。 优化的思路应该是,先统计所有可能的子串出现的次数,然后根据次数和长度计算总长度。这样可以用哈希表或者字典来记录每个子串出现的次数,避免重复计算。 那具体怎么做呢?比如,遍历所有可能的子串(长度1到10),然后统计每个子串出现的次数。这里可以用一个unordered_map<string, int>来保存每个子串出现的次数。这样只需要遍历每个可能的子串一次,统计次数,然后之后直接查表即可。 但是这样的话,如何遍历所有可能的子串呢?对于每个i(长度),遍历每个j的位置,截取子串,然后在哈希表中计数。这样时间复杂度是O(10*N),因为对于每个i,最多有N个位置,每个i最多到10。然后总的时间复杂度是O(10*N),这比原来的O(10*N^2)要好很多。因为原来的代码对于每个子串都要调用figure函数,而figure的时间是O(N),所以总时间是O(10*N^2),而优化后的统计次数的时间是O(10*N),然后遍历哈希表找最大值的时间可以忽略不计。 所以,优化方案应该是预先生成所有可能的子串(长度1到10),并用哈希表记录它们的出现次数。这样可以避免重复调用figure函数,从而减少时间复杂度。 现在,原来的figure函数每次都会从头开始查找,统计非重叠出现的次数。比如,对于子串"aaa",在字符串"aaaa"中,当子串长度为1时,每个位置都是"a",出现次数是4;但如果子串长度是2,比如"aa",那么统计的时候会找到两次(位置0和2)。所以figure函数统计的是非重叠的出现次数,每次找到之后pos +=子串长度,这样就不会重叠。 但是在用哈希表统计的时候,如果我们直接遍历每个i长度的子串,并统计所有可能的出现次数,那么如何得到非重叠的次数呢?比如,原来的做法是,每个子串的出现次数是figure函数计算的非重叠次数。而用哈希表的话,如果直接统计所有子串的出现次数,那么比如子串m出现的位置可能有重叠的情况,比如"aaa"中的子串"aa"在位置0和1,这样统计出来的次数是两次,而原来的figure函数统计的是两次吗?或者原figure函数是怎么处理的? 原figure函数的逻辑是,每次找到子串后,pos +=子串的长度,所以是非重叠的。例如,假设s是"ababa",子串是"aba",那么第一次找到在位置0,然后pos变成0+3=3,然后从位置3开始找,找到在位置2?不,因为substr(j,i)是i长度的子串。比如,原代码中m是s.substr(j,i),i是子串长度。比如,当i=3,j=0时,m是前三个字符。当调用figure(m, i)时,在s中查找m的所有非重叠出现次数。每次找到后,pos增加i的长度,所以不会重叠。 但是如果我们用哈希表的话,如何正确统计非重叠的出现次数呢?例如,对于子串m,要统计它在s中的非重叠出现次数。这时候,如果用预先生成所有可能的子串,并统计次数的话,可能会有问题,因为同一个子串可能在多个不同的起始位置出现,但可能有重叠。例如,字符串"aaaa",子串"aa",出现的非重叠次数是两次(位置0和2)。而如果用哈希表直接统计所有子串的出现次数,比如遍历每个可能的起始位置j,截取长度为i的子串,并计数,那么对于每个j,不管是否重叠,都会被计数一次。例如,j=0时截取"aa",计数加1;j=1时截取"aa",计数加1;这样总共有三个可能的子串(当i=2时,j的范围是0到2,所以三个子串:0-1,1-2,2-3)。但这样的话,哈希表中的计数是每个子串的出现次数是按照每个起始位置j是否出现该子串,而不管是否重叠。这显然和原代码中的figure函数不同,原代码中的figure统计的是非重叠出现的次数。 所以问题出现了:原代码中的figure函数统计的是子串m在整个s中的非重叠出现次数,而如果改用哈希表的方式,每个子串的出现次数会被统计为它在所有可能的起始位置出现的次数(可能有重叠),这显然与原意不符。因此,这种优化方式可能无法正确得到figure函数的统计结果,从而影响最终结果。 这说明原来的思路有问题,即无法简单地用哈希表统计每个子串的出现次数,因为原代码中的figure函数统计的是非重叠的次数,而哈希表的方式统计的是每个子串作为不同起始位置出现的次数,而这些可能重叠。 那怎么办呢?这时候可能需要寻找其他优化方式。 回到原代码的结构: 对于每个i(子串长度),遍历所有可能的子串m(起始位置j),然后调用figure(m, i)计算该子串在s中的非重叠出现次数。然后计算i*num,如果更大则更新max_val和k。 figure函数的逻辑是,每次找到子串后,pos += i,这样保证后续的查找不会重叠。例如,当m的长度是i时,每次找到后,pos增加i,所以下一个查找的位置是当前找到的位置的后面i的位置,从而不重叠。 现在的问题是,原代码对每个可能的子串m都要调用一次figure函数,而figure函数的时间是O(N/i)的时间,因为每次找到之后pos增加i,所以总的查找次数大约是N/i次。但因为i的范围是1到10,所以对于每个i来说,每个j对应的m的figure调用时间是O(N/i)。而每个i对应的j的数量是O(N),所以总的时间复杂度是O(10*N*(N/i))的平均,这大约是O(10*N^2 / 1)(当i=1时,每个j对应O(N)的时间),即总时间复杂度是O(N^2),当N很大时,比如1e4的话,这会是1e8次操作,可能会超时。 所以需要优化figure函数的调用次数,或者找到更高效的方式来计算每个子串的非重叠出现次数。 可能的优化思路: 1. 预计算所有可能的子串的非重叠出现次数,避免重复计算。例如,对于相同的子串,可能在不同的j位置被多次处理,但每次都需要重新计算figure(m,i)。如果能将这些相同的子串的非重叠次数记录下来,可以节省时间。 例如,当i=2时,可能有多个j对应的子串m是相同的。比如,字符串"ababab",当i=2时,j=0得到"ab",j=2得到"ab",j=4得到"ab"。这时候,每个这样的子串都会被处理三次,而每次调用figure(m,2)都会遍历整个字符串计算次数。这时候,可以缓存已经处理过的子串,保存它们的出现次数,避免重复计算。这可以通过一个哈希表来缓存,键是子串,值是出现次数。这样,对于重复的子串,只需要计算一次。 这样的优化是否有效?假设有很多重复的子串,那么可以节省很多时间。例如,当原字符串有很多重复的子串时,缓存可以显著减少时间。反之,如果每个子串都是唯一的,那么缓存无法节省时间,反而增加哈希表的维护开销。但通常来说,对于较长的字符串,可能会有较多重复的子串,所以这样的优化应该有效。 所以,修改代码的方法可以是:在每次处理子串m的时候,先检查是否已经计算过它的出现次数。如果是,直接使用缓存的值;否则,调用figure函数计算,并将结果存入缓存。 这需要引入一个缓存结构,比如unordered_map<string, int> cache。然后在调用figure之前,先查找cache中是否有m对应的次数。如果有,直接使用;否则,计算并存入缓存。 这样,可以避免重复计算相同的子串,从而减少时间。例如,当i=2时,所有相同的子串m只需要计算一次。 这样的优化对于测试用例中有大量重复子串的情况非常有效。例如,原字符串是"aaaaa...",那么所有长度为2的子串都是"aa",所以缓存会命中,只需要一次计算。 这样优化后的时间复杂度会降低多少?假设每个i对应的子串中有k个不同的子串,那么每个i的处理时间是k*O(N/i) + O(N)。因为对于每个不同的子串,需要O(N/i)的时间来计算次数,而每个i对应的j的数量是O(N)。所以总的时间复杂度为O(10*(sum_{i=1 to 10} (k_i * N/i + N))),其中k_i是i长度下不同子串的数量。如果k_i远小于N,那么优化效果明显。 因此,引入缓存机制可以显著减少运行时间。 另一个可能的优化点是,原代码中的figure函数可以优化吗? 当前的figure函数逻辑是: int figure(const string& a, int n) { int sum=0; size_t pos=0; while((pos = s.find(a, pos)) != string::npos) { sum++; pos += a.length(); // 这里应该是n?因为a的长度是n? } return sum; } 这里的参数n似乎没有用到,因为a是子串,其长度可以通过a.length()得到。原代码中的参数n可能是多余的。但是原函数中的pos += a.length(),也就是每次找到后,跳过子串的长度,避免重叠。比如,如果子串长度是i,那么每次找到后,pos +=i。这正确吗? 假设子串是a,那么每次找到之后,pos应该增加a.length(),即i,这样才能跳过已经匹配的部分,避免重叠。例如,字符串"ababa",寻找"aba",第一次在位置0找到,然后pos增加3,变成3,然后从位置3开始找,找不到。所以总共有一次匹配。而如果不跳过的话,可能找到位置0和2。 所以,原figure函数是正确的,因为它计算的是非重叠的次数。因此,在figure函数中,参数n可能多余,可以去掉,直接用a.length()。 但原代码中调用figure(m, i),而i是m的长度。所以参数n是多余的,可以去掉。修改figure函数为: int figure(const string& a) { int sum=0; size_t pos=0; while((pos = s.find(a, pos)) != string::npos) { sum++; pos += a.length(); } return sum; } 这样可能会更简洁,同时减少错误。 但无论如何,这个函数的逻辑是正确的。现在的问题是,每次调用这个函数的时间是O(M),其中M是字符串s的长度。而每次调用都需要遍历整个s,所以总的时间复杂度很高。 所以,引入缓存机制是必须的。那我们可以修改代码,在每次处理一个子串m时,先查看缓存中有无这个子串的次数,如果有,直接取;否则,调用figure函数计算,并将结果存入缓存。 这样,修改后的主循环部分: unordered_map<string, int> cache; for(int i=1; i<=min(...); ++i) { for(int j=0; ...) { string m = s.substr(j,i); int num; if (cache.count(m)) { num = cache[m]; } else { num = figure(m); cache[m] = num; } // 然后比较并更新max_val等 } } 这样,每个不同的子串m只会被计算一次,后续的相同子串直接从缓存中取次数,节省时间。 这可能是一个有效的优化方式。 此外,另一个可能的优化是,预先生成所有可能的子串的非重叠出现次数,而不用多次调用find函数。例如,可以使用KMP算法或者利用字符串哈希的方法,快速统计每个子串的出现次数。但这可能比较复杂。 或者,可以利用更高效的方法来统计所有子串的出现次数,比如使用后缀自动机或者哈希预处理。但这样的方法可能需要较复杂的实现。 另一个思路是,对于每个可能的子串长度i,预处理所有可能的子串,并统计它们的非重叠出现次数。例如,遍历整个字符串一次,记录每个可能的子串的非重叠出现次数。这可能比较困难,因为非重叠的条件需要处理。 比如,对于i=2,我们可以遍历字符串,每次找到子串后,跳过i个字符。例如,用指针的方式:初始位置0,检查子串s[0..1],然后记录次数,然后指针跳到2;检查子串s[2..3],依此类推。这样,统计每个子串的非重叠出现次数。 但是这样做的话,每个子串的长度i需要单独处理,并且需要遍历整个字符串一次,这可能的时间复杂度是O(10*N),这可能是可以接受的。 比如,对于每个i从1到10: 创建一个哈希表count,记录每个子串的非重叠出现次数。 初始化pos=0。 while pos <= s.length() -i: 取子串m = s.substr(pos, i) count[m]++ pos +=i 然后,对于每个可能的起始位置j(不管是否重叠的情况),此时对应的子串m=s.substr(j,i)的非重叠次数可能不是这样计算的。所以这个方法可能不正确,因为原代码中的figure函数统计的是所有可能的非重叠出现,而不仅仅是那些从pos=0, i, 2i, ...的位置开始的子串。例如,原代码中的figure函数可以找到任何位置的非重叠出现,而不仅仅是起始位置为0, i, 2i等的子串。 比如,原figure函数可以找到子串在位置1,然后下一个查找从位置1+i开始,所以可能找到更多的非重叠出现。 因此,这种预处理方法无法正确统计所有可能的非重叠出现次数,因为它只统计了以i为步长的子串,而原代码中的figure函数可以找到任何位置的子串,只要不重叠。 所以这种预处理方法不可行。 那回到之前的思路:缓存已经计算过的子串的出现次数。 这样的优化应该可以显著减少运行时间,特别是当有很多重复的子串时。例如,当原字符串有很多重复的子串时,比如全由同一个字符组成,或者周期性字符串,那么缓存会大大减少计算次数。 现在,我们可以尝试修改原代码,添加缓存: 在全局或者主函数内部,定义一个unordered_map<string, int> cache;。 然后在循环中,每次处理子串m时,先检查cache中是否存在m: if (cache.find(m) != cache.end()) { num = cache[m]; } else { num = figure(m, i); // 注意,原figure函数需要修改,去掉参数n,或者保留,但这里的i和m的长度可能重复? cache[m] = num; } 这样就可以缓存结果。 这样修改后的代码会比原代码更快,因为避免重复计算相同的子串。 另外,另一个优化点:原代码中的figure函数中的第三个参数是i,但函数内部并没有使用它,而是使用a.length()。这可能是一个错误,因为当m的长度是i时,a.length()等于i,所以不影响结果。但参数n可能多余,可以删除。这样修改figure函数: int figure(const string& a) { int sum=0; size_t pos=0; while( (pos = s.find(a, pos)) != string::npos ) { sum++; pos += a.length(); } return sum; } 然后在主函数中调用时,传入m,而不是i。这样可以避免传多余的参数。 这将使代码更简洁,同时可能减少错误。 另一个可能的优化是,将s作为全局变量,或者在figure函数中使用全局变量。原代码中的s是全局变量吗?原代码中,s是全局变量,所以figure函数可以直接访问它。是的,原代码中s是全局变量,所以figure函数可以访问它。所以没问题。 综上,优化后的代码应该: 1. 使用缓存(unordered_map)来存储每个子串的非重叠出现次数,避免重复计算。 2. 修改figure函数,使其不需要参数n,直接使用a.length()。 此外,还有其他可能的优化吗? 例如,原代码中的主循环中,对于每个i,遍历j的所有可能的起始位置(从0到s.length()-i),然后截取子串m。但是,如果两个不同的j对应的子串m相同,那么它们的出现次数是相同的。所以,对于每个i,可以先统计所有可能的子串,并记录它们的出现次数,然后遍历每个唯一的子串,计算总长度,并更新max_val。这样,可以避免处理相同的子串多次。例如,对于i=2,某个子串m出现k次不同的j位置,那么这些j对应的m是相同的,它们的出现次数是相同的,只需要计算一次。然后,这些不同的j对应的处理结果都是相同的i*num,所以只需要处理一次即可。 但是原代码中,不同的j对应的子串m可能相同,但它们的总出现次数是相同的。所以,如果对于每个i,遍历所有可能的唯一子串,并计算它们的出现次数,那么可以节省时间。例如,对于i=2,使用哈希表统计每个子串的出现次数(即有多少个j对应的m是相同的),然后对于每个唯一的子串m,出现次数是count[m]。但是这里的count[m]是j的个数吗?或者说,这里需要统计的是每个子串m在s中的非重叠出现次数,而不是有多少个j对应的起始位置。 例如,对于i=2,如果子串m在s中出现num次(非重叠),那么每个j对应的m可能不同,或者可能相同。原代码中,每个j对应的m是不同的,因为j是起始位置,所以即使m相同,它们来自不同的j。所以,原代码中,对于同一个m,可能有多个j对应,但每个j对应的m的出现次数是figure(m)的值,即该m在整个s中的非重叠出现次数。例如,对于不同的j1和j2,如果它们对应的m相同,那么figure(m)的结果是相同的。所以,在优化的时候,可以对于每个i,先收集所有唯一的子串m,计算它们的出现次数,然后对于每个唯一的m,计算i*num,并比较是否需要更新max_val。这样可以减少循环次数,尤其是当有很多重复子串的时候。 比如,对于i=2,遍历所有可能的j,将子串m存入一个unordered_set中,这样得到所有唯一的子串m。然后对于每个唯一的m,计算其figure(m)的次数,然后乘以i得到总长度。然后,如果有多个j对应的m相同,那么它们的总长度都是i*num,只需要处理一次即可。这可以节省大量的时间,因为内层循环的次数从O(N)减少到O(唯一子串的数量)。 这可能比原代码中的方式更高效,尤其是当i较大时,比如i=10,此时每个i对应的子串数量可能远小于N。 所以,这可能是一个更有效的优化方式。例如,修改主循环的结构: for i from 1 to min(len(s),10): unique_substrings = set of all possible substrings of length i in s for each m in unique_substrings: num = figure(m) total = i * num if total > max_val: update max_val and k elif total == max_val and m < k: update k 这样,内层循环的次数从O(N)减少到O(唯一子串的数量)。这在很多情况下会减少时间,尤其是当i较大时,因为子串的唯一数量可能减少。 例如,当i=10时,可能有很多重复的子串,所以唯一的数量可能远小于N-10+1。 这样的优化可以显著减少循环次数,特别是当字符串有很多重复的子串时。 但如何收集每个i对应的唯一子串呢?可以用一个unordered_set来存储: for (int i=1; ...) { unordered_set<string> unique_subs; for (int j=0; j <= s.length()-i; ++j) { string m = s.substr(j, i); unique_subs.insert(m); } for (auto& m : unique_subs) { // 计算num,并比较 } } 这样,每个i的处理时间包括遍历所有j来收集唯一子串,时间复杂度是O(N)。然后,处理每个唯一子串的时间是O(M),其中M是字符串长度,总时间复杂度为O(N + K*M),其中K是唯一子串的数量。这可能与原代码的时间复杂度相当,甚至更差,因为原代码的内层循环次数是O(N),但每个循环中处理的时间是O(M),而优化后的代码可能需要O(N) + O(K*M)。例如,当K*M > N,这可能更慢。 例如,当i=1时,每个字符都是一个子串,可能有多个重复。比如字符串"aaaaa...",则unique_subs的大小是1,处理时间变为O(N) + 1*O(M) = O(N + M)。原代码的时间复杂度是O(N*M)。当N=1e5,M=1e5时,优化后的时间是O(2e5),而原代码是O(1e10),所以优化明显。但当i=10,且每个子串都唯一时,K=O(N),此时优化后的时间复杂度是O(N + N*M) = O(N*M),与原代码相同,但实际可能更慢,因为需要两次遍历。 所以,这样的优化是否有效取决于K的大小。如果K远小于N,则有效;否则可能无效。 这可能不适用于所有情况,但对于很多情况可能有效。不过,这可能比原代码更快,因为原代码对于每个j都调用figure,而优化后的代码对于每个唯一子串调用一次figure。当有很多重复的子串时,这可以节省时间。 例如,原代码中的每个i循环中,如果有m个子串重复,则每个重复的m都会被多次计算figure。而优化后的代码只计算一次。这明显减少时间。 因此,这样的优化可能更有效。此外,结合缓存,可以进一步优化,即在计算figure(m)的时候,使用缓存,避免重复计算不同i下的相同子串。例如,一个子串可能在不同的i下出现,比如当i=2时子串"ab",可能在i=2时被处理,然后在i=3时作为子串的一部分,但不会作为i=3的子串。所以缓存可以跨不同的i使用。 综上,优化方案可以是: 1. 对于每个i,收集所有唯一的子串。 2. 对于每个唯一的子串m,使用缓存来记录其非重叠出现次数。 3. 计算总长度,并更新max_val和k。 这样,可以同时减少内层循环的次数和figure函数的调用次数。 另一个可能的优化是,预处理所有可能的子串的出现次数。例如,对于所有可能的长度i(1到10),预处理所有子串的非重叠出现次数。这可能比较复杂,但可以尝试。 例如,可以使用滑动窗口的方法,但需要处理非重叠的条件。但非重叠的条件使得预处理变得困难,因为子串的出现位置会影响后续的处理。 比如,对于每个子串的出现,必须确保它们不重叠。这可能导致动态规划的方法,但难以实现。 或者,可以预处理每个可能的子串的所有出现位置,并记录哪些位置是不重叠的。但这需要大量的存储空间,可能不现实。 回到之前的思路,结合缓存和唯一子串的处理,可能是一个可行的优化方向。 现在,回到原代码中的另一个问题:每次截取子串m的时候,是否可以使用更高效的方法?比如,使用字符串哈希或者其他方法快速比较子串? 这可能比较复杂,但对于较长的子串可能有效。但可能不如使用缓存和唯一子串的处理有效。 综上,主要的优化点应该是: 1. 使用缓存存储每个子串的非重叠出现次数,避免重复计算。 2. 对于每个i,收集所有唯一的子串,减少循环次数。 现在,尝试出优化后的代码: #include <bits/stdc++.h> using namespace std; using ll = long long; string s, k; ll max_val = 0; unordered_map<string, int> cache; int figure(const string& a) { int sum = 0; size_t pos = 0; while ((pos = s.find(a, pos)) != string::npos) { sum++; pos += a.length(); } return sum; } int main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> s; int max_len = min((int)s.length(), 10); for (int i = 1; i <= max_len; ++i) { unordered_set<string> unique_subs; for (int j = 0; j <= (int)s.length() - i; ++j) { unique_subs.insert(s.substr(j, i)); } for (const auto& m : unique_subs) { int num; if (cache.count(m)) { num = cache[m]; } else { num = figure(m); cache[m] = num; } ll current = (ll)i * num; if (current > max_val || (current == max_val && m < k)) { if (current > max_val) { max_val = current; k = m; } else { k = min(k, m); } } } } cout << k << '\n'; } 这样的代码是否正确? 让我们看原代码的逻辑。原代码中,对于每个i,遍历所有可能的j,计算对应的m的出现次数,然后比较i*num是否更大。如果有多个m得到相同的max_val,取字典序最小的。 在优化后的代码中,对于每个i,遍历所有唯一的子串m,计算它们的出现次数,然后比较i*num。如果有更大的值,更新max_val和k。这样,是否可能遗漏某些j对应的m? 比如,假设同一个m可能有不同的出现次数,但这是不可能的,因为对于同一个m,figure函数返回的次数是相同的。所以,对于同一个m,无论它来自哪个j,它的出现次数都是figure(m)。因此,在唯一子串的集合中处理每个m一次即可,无需处理所有的j。因为无论有多少个j对应的m相同,它们的总长度是i*num,所以只需要处理一次。 因此,优化后的代码是正确的。因为对于每个i,所有可能的m都被处理过,并且每个m的总长度i*num被正确计算,并且比较后取最大的,字典序最小的。 这样的优化将内层循环的次数从O(N)减少到O(K),其中K是唯一子串的数量。同时,结合缓存,每个唯一的m的figure函数只被调用一次。 这将显著减少运行时间,尤其是当有很多重复子串时。 现在,测试这样的优化是否有效。例如,当s是"ababababab",i=2时,所有可能的子串是"ab", "ba",每个出现多次。原代码会遍历每个j的位置,每个都调用figure函数,而优化后的代码只需要处理每个唯一的子串两次,每个调用一次figure函数,其他相同子串的j位置将被忽略,从而节省时间。 另一个可能的优化点:在收集唯一子串时,可以使用滑动窗口和滚动哈希来减少字符串复制的开销。例如,对于每个i,遍历j,计算当前子串的哈希值,并将哈希值存入集合中,而不是存储子串本身。这可能减少内存和时间,因为字符串比较和存储可能比较耗时。但需要处理哈希冲突的问题,或者使用双重哈希来降低冲突概率。这可能比较复杂,但对于特别大的字符串可能有效。不过,对于一般的题目,可能不需要这样的优化,而使用字符串本身作为键已经足够。 综上,优化后的代码将使用缓存和唯一子串处理,从而减少figure函数的调用次数和内层循环的次数,从而降低总体的运行时间。</think>以下是优化后的代码,主要通过引入缓存和减少重复计算来提升效率: ```cpp #include <bits/stdc++.h> using namespace std; using ll = long long; string s, k; ll max_val = 0; unordered_map<string, int> cache; int count_occurrences(const string& sub) { int cnt = 0; size_t pos = 0; while ((pos = s.find(sub, pos)) != string::npos) { ++cnt; pos += sub.length(); // 跳过已匹配部分,避免重叠 } return cnt; } int main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> s; int max_len = min((int)s.size(), 10); for (int len = 1; len <= max_len; ++len) { unordered_set<string> unique_subs; // 收集所有唯一子串 for (int i = 0; i <= (int)s.size() - len; ++i) { unique_subs.insert(s.substr(i, len)); } // 处理每个唯一子串 for (const auto& sub : unique_subs) { int cnt; if (cache.count(sub)) { cnt = cache[sub]; } else { cnt = count_occurrences(sub); cache[sub] = cnt; } ll current = (ll)len * cnt; if (current > max_val || (current == max_val && sub < k)) { max_val = current; k = sub; } } } cout << k << '\n'; } ``` 主要优化点说明: 1. **缓存机制** 使用 `unordered_map` 缓存每个子串的统计结果,避免重复调用查找函数。对于重复出现的子串,直接使用缓存值,时间复杂度从 O(N) 降为 O(1)。 2. **唯一子串预处理** 对每个长度 `len`,先用集合收集所有唯一子串,减少后续处理次数。例如字符串 "aaaaa" 在 `len=2` 时只需处理 "aa" 一次,而非四次。 3. **逻辑简化** - 移除冗余参数,直接通过子串长度计算步长 - 合并比较逻辑,避免重复判断字典序 优化后时间复杂度从 O(10·N²) 降至约 O(10·K·N),其中 K 为唯一子串数量。在字符串重复较多时,效率提升显著。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值