研究这个的动机是因为 https://leetcode.com/problems/longest-happy-prefix/ 这个周赛题不会
然后看了很多网上讲解 KMP 的博客,发现还是不理解。 因为很多到了求“最长相等前后缀”这一步就略过了或者讲的不好理解。
这里 next[i] 等于 s[0..i] 这个子串的最长相等前后缀。
目的是希望根据 next[0..i] 和 s[0..i+1] 求 next[i+1]
因为 s[0...next[i]] == s[(i-next[i]+1)..i],所以当 s[next[i]] == s[i+1] 的时候,我们找到了匹配,有 next[i+1] = next[i] + 1 。
比如 "ccbccc" 这个字符串有 next = [0, 1, 0, 1, 2, 2],考察 i = 3 时,next[3] == 1, s[1] == 'c' == s[4],所以 next[4] = next[3] + 1 = 2。
难点是如果 s[next[i]] != s[i+1],应该如何计算 next[i+1]。
首先显然 next[i+1] 会小于等于 next[i]。即如果有匹配,s[i+1] 会匹配上 s[0...next[i]] 之间的某一个字符,也就是
匹配的位置会向前移动
问题是移到哪个位置 j,可以确保 s[0...j] == s[(i-j+1)..i] 且 j 最大,这样可以再次通过比较 s[j] 和 s[i+1] 来确定 next[i+1] 的值。
s[0...j] 是 s[0..i] 的一个前缀,s[(i-j+1)..i] 是 s[0..i] 的一个后缀,且满足 s[0...j] == s[(i-j+1)..i],j < next[i]
换句话说,我希望找到s[0..i]的一个第二长的相等前后缀来尝试匹配,如果不匹配,就尝试找第三长的,最后可能和 s 的第一个字符比较。
s[0..i]最长的相等前后缀长度是 next[i],次长是什么呢?
应该是 s[0...next[i]] 的最长相等前后缀的长度
代换一下变量,s[0...next[i]] == s[0..(next[i] - 1)] => 次长为 next[next[i] - 1] => j = next[next[i] - 1]
第三长的为 s[0...next[next[i] - 1]] 的最长相等前后缀的长度
以此类推
j = next[next[i] - 1] // 第二长
j = next[j - 1] // 第三长
比如 "ccbccc" 这个字符串有 next = [0, 1, 0, 1, 2, 2],考察 i = 4 时,next[4] == 2, s[2] == 'b' != s[5],这时尝试检查 j = next[next[4] - 1] = next[1] = 1 位置 s[j] = s[1] = 'c' = s[i + 1],匹配成功,next[5] = j + 1 = 1 + 1 = 2
最后贴一段代码
impl Solution {
pub fn longest_prefix(s: String) -> String {
let cs: Vec<char> = s.chars().collect();
let mut next = vec![0; s.len()];
for i in 1..s.len() {
// find match
if cs[next[i-1]] == cs[i] {
next[i] = next[i-1] + 1;
continue;
}
if next[i-1] == 0 { continue; }
// not find, decrease j to try match
let mut j = next[next[i-1] - 1];
while j >= 0 {
if cs[j] == cs[i] {
next[i] = j + 1;
break;
}
if j == 0 { break; }
j = next[j-1];
}
}
s[0..next[s.len() - 1]].to_string()
}
}