--- comments: true difficulty: 困难 edit_url: https://github.com/doocs/leetcode/edit/main/solution/1300-1399/1316.Distinct%20Echo%20Substrings/README.md rating: 1836 source: 第 17 场双周赛 Q4 tags: - 字典树 - 字符串 - 哈希函数 - 滚动哈希 --- # [1316. 不同的循环子字符串](https://leetcode.cn/problems/distinct-echo-substrings) [English Version](/solution/1300-1399/1316.Distinct%20Echo%20Substrings/README_EN.md) ## 题目描述

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

例如,abcabc 就是 abc 和它自身连接形成的。

 

示例 1:

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

示例 2:

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

 

提示:

## 解法 ### 方法一:字符串哈希 **字符串哈希**是把一个任意长度的字符串映射成一个非负整数,并且其冲突的概率几乎为 0。字符串哈希用于计算字符串哈希值,快速判断两个字符串是否相等。 取一固定值 BASE,把字符串看作是 BASE 进制数,并分配一个大于 0 的数值,代表每种字符。一般来说,我们分配的数值都远小于 BASE。例如,对于小写字母构成的字符串,可以令 a=1, b=2, ..., z=26。取一固定值 MOD,求出该 BASE 进制对 M 的余数,作为该字符串的 hash 值。 一般来说,取 BASE=131 或者 BASE=13331,此时 hash 值产生的冲突概率极低。只要两个字符串 hash 值相同,我们就认为两个字符串是相等的。通常 MOD 取 2^64,C++ 里,可以直接使用 unsigned long long 类型存储这个 hash 值,在计算时不处理算术溢出问题,产生溢出时相当于自动对 2^64 取模,这样可以避免低效取模运算。 除了在极特殊构造的数据上,上述 hash 算法很难产生冲突,一般情况下上述 hash 算法完全可以出现在题目的标准答案中。我们还可以多取一些恰当的 BASE 和 MOD 的值(例如大质数),多进行几组 hash 运算,当结果都相同时才认为原字符串相等,就更加难以构造出使这个 hash 产生错误的数据。 #### Python3 ```python class Solution: def distinctEchoSubstrings(self, text: str) -> int: def get(l, r): return (h[r] - h[l - 1] * p[r - l + 1]) % mod n = len(text) base = 131 mod = int(1e9) + 7 h = [0] * (n + 10) p = [1] * (n + 10) for i, c in enumerate(text): t = ord(c) - ord('a') + 1 h[i + 1] = (h[i] * base) % mod + t p[i + 1] = (p[i] * base) % mod vis = set() for i in range(n - 1): for j in range(i + 1, n, 2): k = (i + j) >> 1 a = get(i + 1, k + 1) b = get(k + 2, j + 1) if a == b: vis.add(a) return len(vis) ``` #### Java ```java class Solution { private long[] h; private long[] p; public int distinctEchoSubstrings(String text) { int n = text.length(); int base = 131; h = new long[n + 10]; p = new long[n + 10]; p[0] = 1; for (int i = 0; i < n; ++i) { int t = text.charAt(i) - 'a' + 1; h[i + 1] = h[i] * base + t; p[i + 1] = p[i] * base; } Set vis = new HashSet<>(); for (int i = 0; i < n - 1; ++i) { for (int j = i + 1; j < n; j += 2) { int k = (i + j) >> 1; long a = get(i + 1, k + 1); long b = get(k + 2, j + 1); if (a == b) { vis.add(a); } } } return vis.size(); } private long get(int i, int j) { return h[j] - h[i - 1] * p[j - i + 1]; } } ``` #### C++ ```cpp typedef unsigned long long ull; class Solution { public: int distinctEchoSubstrings(string text) { int n = text.size(); int base = 131; vector p(n + 10); vector h(n + 10); p[0] = 1; for (int i = 0; i < n; ++i) { int t = text[i] - 'a' + 1; p[i + 1] = p[i] * base; h[i + 1] = h[i] * base + t; } unordered_set vis; for (int i = 0; i < n - 1; ++i) { for (int j = i + 1; j < n; j += 2) { int k = (i + j) >> 1; ull a = get(i + 1, k + 1, p, h); ull b = get(k + 2, j + 1, p, h); if (a == b) vis.insert(a); } } return vis.size(); } ull get(int l, int r, vector& p, vector& h) { return h[r] - h[l - 1] * p[r - l + 1]; } }; ``` #### Go ```go func distinctEchoSubstrings(text string) int { n := len(text) base := 131 h := make([]int, n+10) p := make([]int, n+10) p[0] = 1 for i, c := range text { t := int(c-'a') + 1 p[i+1] = p[i] * base h[i+1] = h[i]*base + t } get := func(l, r int) int { return h[r] - h[l-1]*p[r-l+1] } vis := map[int]bool{} for i := 0; i < n-1; i++ { for j := i + 1; j < n; j += 2 { k := (i + j) >> 1 a, b := get(i+1, k+1), get(k+2, j+1) if a == b { vis[a] = true } } } return len(vis) } ``` #### Rust ```rust use std::collections::HashSet; const BASE: u64 = 131; impl Solution { #[allow(dead_code)] pub fn distinct_echo_substrings(text: String) -> i32 { let n = text.len(); let mut vis: HashSet = HashSet::new(); let mut base_vec: Vec = vec![1; n + 1]; let mut hash_vec: Vec = vec![0; n + 1]; // Initialize the base vector & hash vector for i in 0..n { let cur_char = ((text.chars().nth(i).unwrap() as u8) - ('a' as u8) + 1) as u64; // Update base vector base_vec[i + 1] = base_vec[i] * BASE; // Update hash vector hash_vec[i + 1] = hash_vec[i] * BASE + cur_char; } // Traverse the text to find the result pair, using rolling hash for i in 0..n - 1 { for j in i + 1..n { // Prevent overflow let k = i + (j - i) / 2; let left = Self::get_hash(i + 1, k + 1, &base_vec, &hash_vec); let right = Self::get_hash(k + 2, j + 1, &base_vec, &hash_vec); if left == right { vis.insert(left); } } } vis.len() as i32 } #[allow(dead_code)] fn get_hash(start: usize, end: usize, base_vec: &Vec, hash_vec: &Vec) -> u64 { hash_vec[end] - hash_vec[start - 1] * base_vec[end - start + 1] } } ```