leetcode 1316. 不同的循环子字符串—(每日一难day18)

1316. 不同的循环子字符串

给你一个字符串 text ,请你返回满足下述条件的 不同 非空子字符串的数目:

可以写成某个字符串与其自身相连接的形式(即,可以写为 a + a,其中 a 是某个字符串)。
例如,abcabc 就是 abc 和它自身连接形成的。

示例 1:

输入:text = “abcabcabc”
输出:3
解释:3 个子字符串分别为 “abcabc”,“bcabca” 和 “cabcab”。

示例 2:

输入:text = “leetcodeleetcode”
输出:2
解释:2 个子字符串为 “ee” 和 “leetcodeleetcode” 。

提示:

1 <= text.length <= 2000
text 只包含小写英文字母。

解析

  • 题目要求不同的循环字符串长度,因此可以枚举长度[2,n/2]。
  • 需要用到set去重,但是将字符串加入set的复杂度为O(N),子串比较的复杂度也是O(N),因此需要用到字符串hash的技巧。
  • 字符串hash,简单来讲就像要将字符串"123"转换为数字123一样,数字的每次进制为10,字符串的可以取26 (一般取大于26的质数,防止冲突)。
  • 我们用pre[i]表示字符串[0:i]表示的hash,那么就可以根据pre将s[i:j]的hash表示出来。怎么表示呢?下边举个例子。

假如:字符串 s=“123”

  1. pre[0]=0
  2. pre[1]=pre[i-1]*10+s[0]-‘0’=1;
  3. pre[2]=pre[i-1]*10+s[1]-‘0’=12;
  4. pre[3]=pre[i-1]*10+s[2]-‘0’=123;

如果想获取子串 “23” 对应的值
对应字符串的下标分别为l=1,r=2
只需要用pre[3]-pre[1]*100=123-1*100=23(也就是pre[r+1]-pre[l]*base[r-l+1])
这里的100由字符串的长度决定,可以用一个mul数组存放不同长度的字符串要乘的底数

  1. mul[0]=1
  2. mul[1]=10
  3. mul[2]=100
  4. mul[3]=1000

到这里我们就能将字符串转换为数字了,但是可以发现字符串如果过长的话正常会溢出,因此需要取余操作。

pre[i] = ((ll)pre[i - 1] * base + text[i - 1]) % mod;
mul[i] = (ll)mul[i - 1] * base % mod;

在通过hash = pre[r+1] - pre[l] * base[r-l+1] 获取hash值时,
有可能会使pre[l] * base[r-l+1]溢出,
因此,hash = pre[r+1] - pre[l] * base[r-l+1]%mod.

由于已经对pre数组元素取余操作,
因此 pre[r+1] - pre[l] * base[r-l+1]%mod* 可能为负数.
例如11%10=1,5%10=5,1-5<0。
所以,hash=( pre[r+1] - pre[l] * base[r-l+1]%mod + mod)*。

到这里就完整的得到了子串对应的hash值.

相同技巧题目

 int gethash(int l,int r,vector<int>& pre,vector<int>& mul){
        return (pre[r + 1] - (ll)pre[l] * mul[r - l + 1] % mod + mod) % mod;
    }

code

typedef long long ll;
class Solution {
    constexpr static int mod = (int)1e9 + 7;
public:
    // 枚举每个长度,然后滑动窗口
    int base=31;
    int res=0;
    int gethash(int l,int r,vector<int>& pre,vector<int>& mul){
        return (pre[r + 1] - (ll)pre[l] * mul[r - l + 1] % mod + mod) % mod;
    }
    void check(string s,int k,vector<int>& pre,vector<int>& mul){
        int n=s.size();
        unordered_set<int> se;
        for(int i=0;i<n-2*k+1;i++){
            int fir=gethash(i,i+k-1,pre,mul);
            int lst=gethash(i+k,i+2*k-1,pre,mul);
            if(fir==lst&&se.find(fir)==se.end()){
                se.insert(fir);
                res++;
            } 
        }
    }

    int distinctEchoSubstrings(string text) {
        int n=text.size();
        if(n==1)
            return 0;
        int base = 31;
        vector<int> pre(n + 1), mul(n + 1);
        pre[0] = 0;
        mul[0] = 1;
        // 用pre[i]表示text[0:i]的hash值,不包括text[i]
        for (int i = 1; i <= n; ++i) {
            pre[i] = ((ll)pre[i - 1] * base + text[i - 1]) % mod;
            mul[i] = (ll)mul[i - 1] * base % mod;
        }

        for(int i=1;i<=n/2;i++){
            check(text,i,pre,mul);
        }
        return res;
    }
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值