Tandem repetitions
Definition
Given a string of length .
The new string produced by writing a string twice consecutively is called a tandem repetition. Below, for precision, we call the string being repeated the original string. In other words, a tandem repetition is equivalent to a pair of indices such that is the concatenation of two identical strings.
Your goal is to find all tandem repetitions in a given string . Alternatively, solve the simpler problem of finding any tandem repetition or the longest tandem repetition in .
The algorithm below was proposed by Michael Main and Richard J. Lorentz in 1982.
Conventions
All string indices below start from .
Below, let denote the reverse of . For example, .
Explanation
Consider the string . It contains three tandem repetitions:
As another example, consider the string . It contains only two tandem repetitions:
Number of tandem repetitions
A string of length may have as many as tandem repetitions. An obvious example is a string consisting of identical letters: in this case, any substring of even length is a tandem repetition. In most cases, a periodic string with a relatively short period has many tandem repetitions.
However, this does not prevent us from calculating the number of tandem repetitions in time. This is because the algorithm expresses a tandem repetition in a compressed form, allowing multiple tandem repetitions to be compressed into one.
Here are some interesting conclusions about the number of tandem repetitions:
- If the original string of a tandem repetition is not itself a tandem repetition, we call this tandem repetition a primitive repetition. It can be proved that there are at most primitive repetitions.
- If we compress a tandem repetition using a Crochemore triple , where is the starting position of the tandem repetition, is the length of one period of the tandem repetition (note that it is not the length of the original string), and is the number of repetitions of this period, then all tandem repetitions of a string can be represented by Crochemore triples.
- Fibonacci strings are defined as follows:
It can be seen that Fibonacci strings are highly periodic. For a Fibonacci string of length , even when compressed using Crochemore triples, there are triples. The number of its primitive repetitions is also .
Main–Lorentz algorithm
Explanation
The core idea of the Main–Lorentz algorithm is divide and conquer.
The algorithm divides the string into left and right parts. It first calculates the number of tandem repetitions completely within the left (or right) part, and then calculates the number of tandem repetitions whose starting position is in the left part and whose ending position is in the right part. (Below, we call such tandem repetitions crossing repetitions.)
Calculating the number of crossing repetitions is the key point of the Main–Lorentz algorithm, and we will examine it in detail below.
Process
Finding crossing repetitions
Let the left and right parts of a string be . Then , and the lengths of and are approximately half the length of .
For any tandem repetition, consider its middle character. Here, we call the first character in the right half of a tandem repetition its middle character; in other words, if is a tandem repetition, its middle character is . If the middle character of a tandem repetition is in , we call it left; otherwise, we call it right.
Next, we will discuss how to find all left repetitions.
Let the length of a left repetition be . Consider the first character of this repetition that falls in (namely ). This character must match some character in .
We fix and find all repetitions satisfying the conditions. For example, for the string (where is used to distinguish the left and right parts), fixing , we can see that the tandem repetition satisfies the requirements.
Clearly, once is fixed, the value of is fixed as well. Once we know how to find all repetitions, we can enumerate from to and find all repetitions satisfying the conditions.
Determining left repetitions
Even after fixing , there may still be multiple tandem repetitions satisfying the conditions. How do we find all of them?
Consider another example. In the string , take the tandem repetition . Let be the length of the substring from the first character of this tandem repetition to , and let be the length of the substring from to the last character of the original string on the left side of the tandem repetition.
Thus, we can give a necessary and sufficient condition for a substring of length to be a tandem repetition:
Let be the largest integer such that , and let be the largest integer such that . Then for every pair satisfying and , we can find exactly one corresponding tandem repetition.
In summary:
- Fix a .
- Then all tandem repetitions we seek have length . There may still be multiple repetitions satisfying the conditions, depending on the values of and .
- Calculate the and mentioned above.
- Then all tandem repetitions satisfying the conditions satisfy:
Next, we only need to consider how to calculate and quickly. With the help of the Z-function, we can calculate them in :
- To calculate : calculate the Z-function of .
- To calculate : calculate the Z-function of , where is a character that appears in neither nor .
Right repetitions
The method for calculating right repetitions is almost identical to that for calculating left repetitions. Consider the first character of this repetition that falls in (namely ). It must match some character in ; let the position of this character in be .
Let be the largest integer such that , and be the largest integer such that . Then we can obtain and by calculating the Z-functions of and , respectively.
Enumerate and find right repetitions in a similar manner.
Implementation
The Main–Lorentz algorithm gives all tandem repetitions in the form of quadruples . If you only need to calculate the number of tandem repetitions or find the longest one, the information in this quadruple is sufficient. By the master theorem, the time complexity of the Main–Lorentz algorithm is .
Note that if you want to use these quadruples to find the starting and ending positions of all tandem repetitions, the worst-case time complexity reaches . We implement this below, storing the starting and ending positions of all tandem repetitions in repetitions.
vector<int> z_function(string const& s) {
int n = s.size();
vector<int> z(n);
for (int i = 1, l = 0, r = 0; i < n; i++) {
if (i <= r) z[i] = min(r - i + 1, z[i - l]);
while (i + z[i] < n && s[z[i]] == s[i + z[i]]) z[i]++;
if (i + z[i] - 1 > r) {
l = i;
r = i + z[i] - 1;
}
}
return z;
}
int get_z(vector<int> const& z, int i) {
if (0 <= i && i < (int)z.size())
return z[i];
else
return 0;
}
vector<pair<int, int>> repetitions;
void convert_to_repetitions(int shift, bool left, int cntr, int l, int k1,
int k2) {
for (int l1 = max(1, l - k2); l1 <= min(l, k1); l1++) {
if (left && l1 == l) break;
int l2 = l - l1;
int pos = shift + (left ? cntr - l1 : cntr - l - l1 + 1);
repetitions.emplace_back(pos, pos + 2 * l - 1);
}
}
void find_repetitions(string s, int shift = 0) {
int n = s.size();
if (n == 1) return;
int nu = n / 2;
int nv = n - nu;
string u = s.substr(0, nu);
string v = s.substr(nu);
string ru(u.rbegin(), u.rend());
string rv(v.rbegin(), v.rend());
find_repetitions(u, shift);
find_repetitions(v, shift + nu);
vector<int> z1 = z_function(ru);
vector<int> z2 = z_function(v + '#' + u);
vector<int> z3 = z_function(ru + '#' + rv);
vector<int> z4 = z_function(v);
for (int cntr = 0; cntr < n; cntr++) {
int l, k1, k2;
if (cntr < nu) {
l = nu - cntr;
k1 = get_z(z1, nu - cntr);
k2 = get_z(z2, nv + 1 + cntr);
} else {
l = cntr - nu + 1;
k1 = get_z(z3, nu + 1 + nv - 1 - (cntr - nu));
k2 = get_z(z4, (cntr - nu) + 1);
}
if (k1 + k2 >= l) convert_to_repetitions(shift, cntr < nu, cntr, l, k1, k2);
}
}
This page is mainly adapted from the blog Поиск всех тандемных повторов в строке. Алгоритм Мейна-Лоренца and its English translation Finding repetitions. The Russian version is licensed under Public Domain + Leave a Link; the English version is licensed under CC-BY-SA 4.0.