最长重复子串:哈希+二分优化算法

目录

前言

一、最长重复子串

1、暴力枚举

2、哈希+二分

小结


前言

在字符串处理中,求最长重复子串是一个经典问题,结合了哈希+二分的优化算法。

一、最长重复子串

题目描述:

重复子串:即在字符串中至少出现两次的子串,输入输出如下所示:

约束条件:

1、暴力枚举

最直接的想法就是暴力枚举,两层for循环枚举所有可能的子串,检查它是否重复出现,代码如下所示:

cpp 复制代码
class Solution
{
public:
    int longestRepeatingSubstring(string s)
    {
        int n=s.size();
        for(int len=n-1;len>=0;len--)
        {
            for(int i=0;i+len<=n;i++)
            {
                string str=s.substr(i,len);
                if(s.find(str,i+1)!=string::npos)
                {
                    return len;
                }
            }
        }
        return 0;
    }
};

外层循环从n-1到0枚举长度,找到第一个满足条件的就直接返回,保证是最长长度。内层循环i枚举所有起始位置,string str=s.substr(i,len),用substr取子串。s.find(str,i+1)从i+1位置开始查找子串,if(s.find(str,i+1)!=string::npos),表明str为重复子串,len为最长长度,return len。

时间复杂度分析:

枚举子串数量:O(n^2)

find:O(n)

总复杂度:O(n^3)

显然O(n^3)在1<=n<=2000这个数据范围内不可过,需对该算法进行优化。

2、哈希+二分

核心观察:如果长度为L的重复子串存在,那么长度为L-1的重复子串也一定存在,只要截取前L-1个字符即可,因此子串长度具有单调性,可以考虑进行二分查找。二分判断的依据通过设计一个哈希函数,计算所有长度为L的子串,存入哈希集合,如果某个子串重复出现,返回true。代码实现如下:

cpp 复制代码
class Solution {
public:
    int longestRepeatingSubstring(string s) {
        int n=s.size();
        int left=0,right=n-1;
        int ans=0;
        while(left<=right)
        {
            int mid=left+(right-left)/2;
            if(hashst(s,mid))
            {
                ans=mid;
                left=mid+1;
            }
            else
            {
                right=mid-1;
            }
        }
        return ans;
    }
private:
    bool hashst(string& s,int len)
    {
        if(len==0) return true;
        unordered_set<string> seen;
        for(int i=0;i+len<=s.size();i++)
        {
            string str=s.substr(i,len);
            if(seen.count(str)) return true;
            seen.insert(str);
        }
        return false;
    }
};

1、二分枚举长度:

0,n-1范围内二分查找最长重复子串的长度,int mid=left+(right-left)/2,取中间值mid,调用hashst函数验证该长度下是否存在重复子串。

2、hashst函数:

unordered_set<string> seen,string str=s.substr(i,len),seen.insert(str),hashst函数内部通过for循环来存储所有len长度的子串。if(seen.count(str)),一旦发现某个子串str已经存在,return true,说明该长度可行。

3、二分决策:

if(hashst(s,mid)),如果当前长度可行,ans=mid,left=mid+1,则尝试更长的长度,else则right=mid-1,不可行则缩短长度继续尝试,最终ans即为最长重复子串的长度。

时间复杂度分析:

二分查找时间复杂度为O(logN),hashst函数内部遍历字符串中所有长度为mid的子串,为O(N),因此总时间复杂度为O(NlogN)。

小结

本道题是字符串处理的经典题,通过观察重复子串长度具有单调性,从而使用二分进行查找,二分判断通过哈希来存储所有长度为len的子串,一旦遇到重复就返回真,说明该长度可行,然后二分继续尝试更长的长度,如果不可行则缩短长度继续尝试,通过二分+哈希优化实现了O(NlogN)的查找效率,是字符串处理的常用解法。