Z Algorithm
Z Algorithm is an algorithm that quickly finds the length of the longest common prefix between each suffix of a string and the entire string in O(N)
1. Description
The Z-array for a string $\text{s}$ is an array where $\text{z[i]}$ represents the length of the longest substring starting from the position $\text{i}$ which is also a prefix of $\text{s}$. The reason the time complexity is $\text{O(n)}$ is due to reusing the results obtained previously.
2. Example Scenario
Consider the string s = “$\text{ababcabab}$”.
The Z-array for this string would be calculated as follows:
Initialization:
Start with $\text{z[0]}$ = 0 because the entire string $\text{s}$ is trivially a prefix of itself. For other positions, compute the longest match with the prefix starting from that position.
Calculating Z-values:
| i | The substring starting at index i | z[i] | 
|---|---|---|
| 0 | $\text{ababcabab}$ | 0 | 
| 1 | $\text{babcabab}$ | 0 | 
| 2 | $\text{abcabab}$ | 2 | 
| 3 | $\text{bcabab}$ | 0 | 
| 4 | $\text{cabab}$ | 0 | 
| 5 | $\text{abab}$ | 4 | 
| 6 | $\text{bab}$ | 0 | 
| 7 | $\text{ab}$ | 2 | 
| 8 | $\text{b}$ | 0 | 
3. Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
    long long sumScores(string s) {
        long long ans=0;
        int n=s.size(),l,r;
        vector<int> z(n);
        l=r=0; 
        z[0]=n;
        ans+=z[0];
        for(int i=1;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;
            ans+=z[i];
        }
        return ans;
    }
};
4. Proof
We will save the maximum of $\text{i+z[i]-1}$ to $\text{r}$, and save $\text{i}$ to $\text{l}$ when $\text{r}$ is updated.
- Case 1: $\text{i} \leq \text{r}$ - The statement $\text{s[i…r]} = \text{s[(i-l)…(r-l)]}$ is valid - Case 1-1: $\text{i} \leq \text{r}$ and $\text{r-i+1} \leq \text{z[i-l]}$ - $\text{r+1}$ is an unexplored index, so we should start exploring from $r+1$ and update $z[i]$ 
- Case 1-2: $i\leq r$ and $r-i+1> z[i-l]$ - $z[i]=z[i-l]$ is valid 
 
- Case 2: $\text{i}>\text{r}$ - $\text{r+1}$ is an unexplored index, so we should start exploring from $\text{r+1}$ and update $\text{z[i]}$ 
In Case 1-2, we don’t enter the while loop because of the condition. In Cases 1-1 and 2, $\text{i+z[i]}$ will always be greater than the previous $\text{r}$. Thus, $\text{i+z[i]}$ will cover every index form $\text{0}$ to $\text{n-1}$ exactly once. Therefore, the overall time complexity is $\text{O(N)}$.