Given a string s
, you are asked to find the longest prefix of s
which is also a suffix (but not equal to the entire string itself). This is known as the "Longest Happy Prefix".
Formally, you need to find the longest non-empty string pre
such that pre
is both a prefix and a suffix of s
, and pre != s
.
Constraints:
s
consists of lowercase English letters.s
is up to 105.At first glance, the problem seems to invite a brute-force approach: for every possible prefix (except the full string), check if it also matches the corresponding suffix. However, this would be inefficient, especially for long strings.
To optimize, we need a way to efficiently compare prefixes and suffixes. This is reminiscent of the string matching algorithms, especially the Knuth-Morris-Pratt (KMP) algorithm, which preprocesses the string to find such overlaps.
The key insight is: for every prefix ending at position i
, we want to know the length of the longest proper prefix which is also a suffix. This information is exactly what the KMP "lps" (longest prefix-suffix) array gives us.
We use the KMP preprocessing algorithm to solve this problem efficiently.
lps
of size n
(where n
is the length of s
), where lps[i]
will store the length of the longest prefix which is also a suffix for the substring s[0..i]
.lps
array.lps[n-1]
gives the length of the longest prefix which is also a suffix (but not the whole string).s[0:lps[n-1]]
as the answer. If lps[n-1]
is 0, we return an empty string.This approach is efficient and avoids redundant comparisons.
Let's take the input s = "level"
.
Another example: s = "ababab"
lps
array is [0,0,1,2,3,4], so the answer is s[0:4] = "abab"
.Brute-force approach:
k
, compare with the corresponding suffix, leading to O(n2) time in the worst case.lps
array takes O(n) time, since we only ever move forward or back by the prefix length.lps
array.The "Longest Happy Prefix" problem asks for the longest prefix of a string that is also a suffix (but not the whole string). While a brute-force solution is possible, it is inefficient for large inputs. By leveraging the KMP algorithm's preprocessing step, we can efficiently compute the answer in linear time. The solution is elegant because it reuses a classic algorithmic technique for a slightly different string overlap problem, making it both efficient and robust.
class Solution:
def longestPrefix(self, s: str) -> str:
n = len(s)
lps = [0] * n
length = 0 # length of the previous longest prefix suffix
for i in range(1, n):
while length > 0 and s[i] != s[length]:
length = lps[length - 1]
if s[i] == s[length]:
length += 1
lps[i] = length
else:
lps[i] = 0
return s[:lps[-1]]
class Solution {
public:
string longestPrefix(string s) {
int n = s.size();
vector<int> lps(n, 0);
int length = 0;
for (int i = 1; i < n; ++i) {
while (length > 0 && s[i] != s[length]) {
length = lps[length - 1];
}
if (s[i] == s[length]) {
++length;
lps[i] = length;
} else {
lps[i] = 0;
}
}
return s.substr(0, lps[n - 1]);
}
};
class Solution {
public String longestPrefix(String s) {
int n = s.length();
int[] lps = new int[n];
int length = 0;
for (int i = 1; i < n; i++) {
while (length > 0 && s.charAt(i) != s.charAt(length)) {
length = lps[length - 1];
}
if (s.charAt(i) == s.charAt(length)) {
length++;
lps[i] = length;
} else {
lps[i] = 0;
}
}
return s.substring(0, lps[n - 1]);
}
}
var longestPrefix = function(s) {
const n = s.length;
const lps = new Array(n).fill(0);
let length = 0;
for (let i = 1; i < n; i++) {
while (length > 0 && s[i] !== s[length]) {
length = lps[length - 1];
}
if (s[i] === s[length]) {
length++;
lps[i] = length;
} else {
lps[i] = 0;
}
}
return s.slice(0, lps[n - 1]);
};