Topics › Strings › Hard String Problems
Longest happy prefix
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
A happy prefix of a string is a non-empty prefix that is also a suffix, excluding the string itself. Given s, return its longest happy prefix, or "" if there is none. The prefix and the suffix are allowed to overlap.
Constraints: 1 ≤ n ≤ 10^5 (the Java file also accepts the empty string and returns ""), s consists of lowercase English letters.
Example 1
s = "level"
answer = "l" // "le" vs "el", "lev" vs "vel", "leve" vs "evel" all differ
Example 2
s = "ababab"
answer = "abab" // prefix s[0..4) and suffix s[2..6) overlap in "ab"
Example 3
s = "abc"
answer = "" // no proper prefix equals a suffix of the same length
In KMP terminology a happy prefix is a border, and the question asks for the longest border of the whole string.
Approach 1: Brute force
Intuition. There are only n - 1 candidate lengths. Try them from the longest down and return the first length len for which the prefix s[0..len) equals the suffix s[n-len..n). Trying longest first means the first success is the answer.
Algorithm.
- For
len = n - 1down to1: ifs[0..len)equalss[n-len..n), returns[0..len). - Return
"".
static String bruteForce(String s) {
int n = s.length();
for (int len = n - 1; len >= 1; len--) {
if (s.regionMatches(0, s, n - len, len)) return s.substring(0, len);
}
return "";
}
Complexity. Time O(n^2): up to n lengths, each compared in up to len steps. On "aaa...ab" every comparison runs almost to the end before hitting the b, giving about n^2 / 2 steps, roughly 5 · 10^9 for n = 10^5. Space O(1) besides the returned string.
Approach 2: Better, rolling hashes
Intuition. The brute force spends O(len) per candidate only to compare two strings. With polynomial prefix hashes, the hash of any substring is available in O(1), so each candidate can be rejected in constant time. Only when the hashes agree do we compare characters, to rule out a collision.
With h[i] the hash of s[0..i) and pw[k] = B^k, the hash of the suffix s[n-len..n) is h[n] - h[n-len]·B^len (mod M), and the hash of the prefix is simply h[len]. The suffix formula follows from Horner’s rule: the hash of the whole string equals the hash of s[0..n-len) shifted left by len base-B digits, plus the hash of the last len characters. Subtracting the shifted part leaves the suffix.
Algorithm.
- Build
handpwin one pass. - For
len = n - 1down to1: compute the suffix hash; if it equalsh[len]and the strings really match, returns[0..len). - Return
"".
static String better(String s) {
final long MOD = 1_000_000_007L, BASE = 131;
int n = s.length();
long[] h = new long[n + 1], pw = new long[n + 1]; // h[i] = hash of s[0..i), pw[i] = BASE^i
pw[0] = 1;
for (int i = 0; i < n; i++) {
h[i + 1] = (h[i] * BASE + s.charAt(i)) % MOD;
pw[i + 1] = pw[i] * BASE % MOD;
}
for (int len = n - 1; len >= 1; len--) {
long suffix = ((h[n] - h[n - len] * pw[len]) % MOD + MOD) % MOD; // hash of s[n-len..n)
if (h[len] == suffix && s.regionMatches(0, s, n - len, len)) return s.substring(0, len);
}
return "";
}
Complexity. Time O(n) expected: one linear pass to build the arrays, then O(1) per candidate. Character comparisons run only on hash matches. The first true match ends the loop, and false matches (collisions) are so rare with M ≈ 10^9 that they add nothing on average. Space O(n) for the two arrays.
Approach 3: Optimal, the KMP prefix function
Intuition. The LPS array from KMP stores, for every prefix s[0..i], the length of its longest proper prefix that is also a suffix. Applied to the full string, its last entry lps[n - 1] is exactly the length of the longest happy prefix. No hashing and no probabilities: the result is exact and the running time is guaranteed linear.
The array is built incrementally. If len is the longest border of s[0..i-1] and s[i] == s[len], the border extends to len + 1. Otherwise, fall back to the next shorter border, which is lps[len - 1] (a border of a border is again a border), and try again until a match or len == 0.
Algorithm.
- Set
len = 0,lps[0] = 0. - For
i = 1 .. n - 1: whilelen > 0ands[i] != s[len], setlen = lps[len - 1]. Ifs[i] == s[len], incrementlen. Storelps[i] = len. - Return
s[0..lps[n-1]).
static String optimal(String s) {
int n = s.length();
if (n == 0) return "";
int[] lps = new int[n];
int len = 0;
for (int i = 1; i < n; i++) {
while (len > 0 && s.charAt(i) != s.charAt(len)) len = lps[len - 1];
if (s.charAt(i) == s.charAt(len)) len++;
lps[i] = len;
}
return s.substring(0, lps[n - 1]);
}
Dry run on s = "ababab":
| i | s[i] | len before | action | lps[i] |
|---|---|---|---|---|
| 1 | b | 0 | differs from s[0] = a |
0 |
| 2 | a | 0 | matches s[0] |
1 |
| 3 | b | 1 | matches s[1] |
2 |
| 4 | a | 2 | matches s[2] |
3 |
| 5 | b | 3 | matches s[3] |
4 |
lps[5] = 4, so the answer is s[0..4) = "abab".
Complexity. Time O(n): len grows by at most one per character and each fallback shrinks it, so the fallbacks total at most n. Space O(n) for the LPS array.
The Z-function gives the same answer from another angle: the longest happy prefix has length n - i for the smallest i ≥ 1 with i + z[i] == n.
Edge cases and pitfalls
- The whole string does not count. Start at
len = n - 1in the brute force and the hash method. The LPS array excludes it by construction. - Overlap is allowed.
"aaaa"has the answer"aaa". Do not require the prefix and suffix to be disjoint. - Single character or empty string. No proper non-empty prefix exists, so return
"". The optimal code guardsn == 0explicitly becauselps[n - 1]would not exist. - Hash arithmetic. Reduce after every multiplication and add
Mafter the subtraction; Java’s%keeps the sign of a negative value. - Scanning direction. Going from the longest length down lets the brute force and the hash method stop at the first success. Going upward also works, but then you must remember the last success and cannot stop early.
- Do not trust hashes alone on adversarial tests. Either verify on a match, as here, or use a double hash.
Related problems
- KMP Algorithm or LPS array builds the full prefix function used in Approach 3.
- Z function offers the alternative linear method mentioned above.
- Shortest Palindrome asks this exact question about
s + "#" + reverse(s). - Hashing In Strings - Theory explains the prefix hashes used in Approach 2.