Sum of Scores of Built Strings
HardPrompt
You are building a string s of length n one character at a time, prepending each new character to the front of the string. The strings are labeled from 1 to n, where the string with length i is labeled si.
- For example, for
s = "abaca",s1 == "a",s2 == "ca",s3 == "aca", etc.
The score of si is the length of the longest common prefix between si and sn (Note that s == sn).
Given the final string s, return the sum of the score of every si.
Example 1:
Input: s = "babab"
Output: 9
Explanation:
For s1 == "b", the longest common prefix is "b" which has a score of 1.
For s2 == "ab", there is no common prefix so the score is 0.
For s3 == "bab", the longest common prefix is "bab" which has a score of 3.
For s4 == "abab", there is no common prefix so the score is 0.
For s5 == "babab", the longest common prefix is "babab" which has a score of 5.
The sum of the scores is 1 + 0 + 3 + 0 + 5 = 9, so we return 9.Example 2:
Input: s = "azbazbzaz"
Output: 14
Explanation:
For s2 == "az", the longest common prefix is "az" which has a score of 2.
For s6 == "azbzaz", the longest common prefix is "azb" which has a score of 3.
For s9 == "azbazbzaz", the longest common prefix is "azbazbzaz" which has a score of 9.
For all other si, the score is 0.
The sum of the scores is 2 + 3 + 9 = 14, so we return 14.
Constraints:
1 <= s.length <= 105sconsists of lowercase English letters.
Approaches
3 approaches with complexity analysis and trade-offs.
This approach directly implements the problem description. We iterate through each possible suffix of the string s, and for each suffix, we compare it character by character with the original string s to find the length of the longest common prefix (LCP). The sum of these lengths is the final answer.
Algorithm
- Initialize
totalScoreto 0. - Get the length of the string,
n. - Loop with an index
ifrom 0 ton-1. Thisirepresents the start of the suffix.- Initialize
currentScoreto 0. - Loop with an index
jfrom 0, as long asi+jis within the bounds of the string.- If
s.charAt(j)is equal tos.charAt(i+j), incrementcurrentScore. - Otherwise, break the inner loop.
- If
- Add
currentScoretototalScore.
- Initialize
- Return
totalScore.
Walkthrough
We iterate from i = 0 to n-1, where n is the length of the string s. Each i represents the starting index of a suffix s[i:].
For each suffix, we start a nested loop to compare s[i+j] with s[j] for j = 0, 1, 2, ....
We count the number of matching characters from the beginning. This count is the score for the suffix starting at i.
We stop comparing and break the inner loop as soon as a mismatch is found or the end of the suffix is reached.
The scores for all suffixes are summed up to get the final result.
For s = "babab", we would first compare "babab" with "babab" (LCP=5), then "abab" with "babab" (LCP=0), then "bab" with "babab" (LCP=3), and so on.
class Solution { public long sumScores(String s) { int n = s.length(); long totalScore = 0; for (int i = 0; i < n; i++) { int currentScore = 0; for (int j = 0; i + j < n; j++) { if (s.charAt(j) == s.charAt(i + j)) { currentScore++; } else { break; } } totalScore += currentScore; } return totalScore; }}Complexity
Time
O(n^2), where n is the length of the string. The nested loops lead to a quadratic number of character comparisons in the worst case (e.g., a string of all identical characters).
Space
O(1), as we only use a few variables to store the counts and indices, not dependent on the input string size.
Trade-offs
Pros
Simple to understand and implement.
Requires no extra space.
Cons
Inefficient for large strings. The quadratic time complexity will lead to a "Time Limit Exceeded" error on platforms like LeetCode for the given constraints.
To optimize the LCP calculation for each suffix, we can use string hashing. We precompute polynomial rolling hashes for all prefixes of the string s. This allows us to find the hash of any substring in O(1) time. For each suffix, we can then use binary search on the possible LCP length. In each step of the binary search, we check if the hash of the prefix of s matches the hash of the prefix of the current suffix.
Algorithm
- Choose two pairs of base and modulus
(p1, m1)and(p2, m2)for rolling hash to reduce collisions. - Precompute powers of
p1andp2. - Precompute prefix hashes for the string
susing both hash functions. This takes O(n) time. - Initialize
totalScoreto 0. - Loop with an index
ifrom 0 ton-1.- Perform a binary search for the LCP length
kin the range[0, n-i]. - For a given length
midin the binary search:- Calculate the hash of the prefix
s[0...mid-1]. - Calculate the hash of the substring
s[i...i+mid-1]. - If the hashes from both hash functions match, it means the substrings are likely identical. We search for a longer LCP by setting
low = mid + 1and updating the potential LCP length. - If they don't match, we search for a shorter LCP by setting
high = mid - 1.
- Calculate the hash of the prefix
- Add the found LCP length to
totalScore.
- Perform a binary search for the LCP length
- Return
totalScore.
Walkthrough
The core idea is to speed up the comparison of two substrings. Instead of character-by-character comparison, we compare their hash values.
First, we precompute the hashes of all prefixes of s. We also precompute powers of a chosen base p. Using two different hash functions (different base and modulus) is recommended to minimize collisions.
Then, for each suffix starting at index i, we want to find the maximum length k such that s[0...k-1] is the same as s[i...i+k-1].
We can binary search for this k in the range [0, n-i].
For a given mid length in the binary search, we calculate the hash of s[0...mid-1] and s[i...i+mid-1] in O(1) time using our precomputed values.
If the hashes match, we try a larger length (low = mid + 1); otherwise, we try a smaller length (high = mid - 1).
The largest k found for each i is the score, which we add to the total.
class Solution { public long sumScores(String s) { int n = s.length(); long p1 = 31, m1 = 1_000_000_007; long p2 = 37, m2 = 1_000_000_009; long[] p_pow1 = new long[n + 1]; long[] p_pow2 = new long[n + 1]; long[] h1 = new long[n + 1]; long[] h2 = new long[n + 1]; p_pow1[0] = 1; p_pow2[0] = 1; for (int i = 0; i < n; i++) { p_pow1[i + 1] = (p_pow1[i] * p1) % m1; p_pow2[i + 1] = (p_pow2[i] * p2) % m2; h1[i + 1] = (h1[i] * p1 + (s.charAt(i) - 'a' + 1)) % m1; h2[i + 1] = (h2[i] * p2 + (s.charAt(i) - 'a' + 1)) % m2; } long totalScore = 0; for (int i = 0; i < n; i++) { int low = 0, high = n - i, lcp = 0; while (low <= high) { int mid = low + (high - low) / 2; if (mid == 0) { low = mid + 1; continue; } long hash1_s1 = h1[mid]; long hash2_s1 = h2[mid]; long hash1_s2 = (h1[i + mid] - (h1[i] * p_pow1[mid]) % m1 + m1) % m1; long hash2_s2 = (h2[i + mid] - (h2[i] * p_pow2[mid]) % m2 + m2) % m2; if (hash1_s1 == hash1_s2 && hash2_s1 == hash2_s2) { lcp = mid; low = mid + 1; } else { high = mid - 1; } } totalScore += lcp; } return totalScore; }}Complexity
Time
O(n log n). Precomputation takes O(n). The main loop runs `n` times, and each iteration involves a binary search that takes O(log n) time.
Space
O(n) to store the precomputed prefix hashes and powers of the base.
Trade-offs
Pros
Significantly faster than the brute-force approach.
Can pass the time limits for the given constraints.
Cons
More complex to implement than the brute-force approach.
Relies on hashing, which has a theoretical (though very small) probability of collision. Using two hash functions makes this probability negligible for typical contest constraints.
Requires extra space for storing hash values and powers.
This problem is a direct application of the Z-algorithm. The Z-algorithm computes a Z-array for a string s, where Z[i] is the length of the longest common prefix between s and the suffix of s starting at index i. The sum of all values in the Z-array is precisely the answer to the problem. The Z-algorithm can compute this array in linear time.
Algorithm
- Create an integer array
zof sizen. - Initialize
l = 0,r = 0which define the boundaries of the current rightmost Z-box. - Loop with
ifrom 1 ton-1:- If
iis within the current Z-box (i <= r), we can initializez[i]tomin(r - i + 1, z[i - l]). This leverages previous computations. - Expand
z[i]by comparings[z[i]]withs[i + z[i]]until a mismatch or the end of the string is found. - If the new match
s[i...i+z[i]-1]extends beyond the current Z-box (i + z[i] - 1 > r), updateltoiandrtoi + z[i] - 1.
- If
- The score for the full string
s(i.e.,z[0]) isn. - The total score is the sum of
nand allz[i]forifrom 1 ton-1.
Walkthrough
The Z-array z for a string s of length n is an array of length n where z[i] is the length of the LCP between s and s[i:].
The score for the suffix s[i:] is exactly z[i]. Therefore, the problem reduces to computing the Z-array and summing its elements.
The Z-algorithm computes this array efficiently in O(n) time by maintaining a "Z-box" [l, r], which is the interval corresponding to the prefix match that extends furthest to the right.
When computing z[i], if i is within the current Z-box [l, r], we can use the already computed z[i-l] value to get an initial estimate for z[i], avoiding redundant comparisons. If i is outside the box, we compute z[i] by naively comparing characters.
The key insight is that the total number of character comparisons is linear because the right boundary r of the Z-box only moves forward.
After computing the Z-array, we simply sum up all its elements. Note that z[0] is the LCP of s with itself, which is n.
class Solution { public long sumScores(String s) { int n = s.length(); int[] z = new int[n]; long totalScore = n; // z[0] is n, add it upfront int l = 0, r = 0; for (int i = 1; i < n; i++) { // If i is inside the current Z-box [l, r] if (i <= r) { z[i] = Math.min(r - i + 1, z[i - l]); } // Naively extend the match while (i + z[i] < n && s.charAt(z[i]) == s.charAt(i + z[i])) { z[i]++; } // If we found a Z-box that extends beyond the current r, update l and r if (i + z[i] - 1 > r) { l = i; r = i + z[i] - 1; } totalScore += z[i]; } return totalScore; }}Complexity
Time
O(n). The algorithm processes the string in a single pass. Although there's a nested `while` loop, the total number of character comparisons is bounded by `2n` because the right pointer `r` only moves forward.
Space
O(n) to store the Z-array.
Trade-offs
Pros
Most efficient solution with linear time complexity.
Elegant and tailored specifically for this type of LCP problem.
Cons
The algorithm can be non-intuitive to understand and implement correctly without prior knowledge.
Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.