咨询:如何利用KMP算法以O(n)复杂度计算使01串成为回文串的最少开头删除次数
用KMP算法解决「从开头删最少字符得回文串」问题
这问题我之前也琢磨过,用KMP来优化确实能把时间复杂度压到O(n),思路其实挺巧妙的,我给你一步步拆解清楚:
问题转化
首先,咱们得把问题换个角度看:要找原字符串中最长的、以字符串结尾的回文子串。因为删掉开头的字符后,剩下的是原串的后缀,只要这个后缀是回文,那删除的字符数就是总长度减去这个最长回文后缀的长度——这肯定是最少的删除量,毕竟保留的回文越长,删的就越少。
KMP前缀函数的作用
KMP里的前缀函数(也就是常说的next数组),核心是对字符串的每个位置,计算「前i+1个字符中,最长的相等前缀和后缀的长度」。这个特性刚好能帮我们找到原串后缀和反转串前缀的最长匹配——而这个匹配的长度,就是我们要找的最长回文后缀的长度。
具体实现步骤
- 构造拼接字符串:把原字符串
S、一个分隔符(比如#,因为原串只有0和1,不会冲突)、反转后的原字符串S_rev拼在一起,得到combined = S + '#' + S_rev。加分隔符是为了避免匹配时跨原串和反转串的情况,确保我们只匹配原串的后缀和反转串的前缀。 - 计算前缀函数:对
combined字符串计算前缀函数数组pi。 - 得到结果:
pi数组的最后一个元素,就是原串中最长回文后缀的长度。用原串总长度减去这个值,就是需要从开头删除的最少字符数。
代码示例(Python)
def compute_prefix_function(s): n = len(s) pi = [0] * n for i in range(1, n): j = pi[i-1] # 回溯找到最长的匹配前缀 while j > 0 and s[i] != s[j]: j = pi[j-1] if s[i] == s[j]: j += 1 pi[i] = j return pi def min_deletions_to_palindrome(s): reversed_s = s[::-1] # 构造带分隔符的拼接字符串,避免跨串匹配 combined = s + '#' + reversed_s pi = compute_prefix_function(combined) # 最长回文后缀的长度就是pi数组最后一个值 max_palindrome_len = pi[-1] return len(s) - max_palindrome_len # 测试用例 print(min_deletions_to_palindrome("01101")) # 输出2:删除前2个字符,剩余"101"是回文 print(min_deletions_to_palindrome("01001")) # 输出1:删除第一个"0",剩余"1001"是回文 print(min_deletions_to_palindrome("0101")) # 输出1:删除第一个"0",剩余"101"是回文
为什么这个方法有效?
因为回文串的定义是「正读和反读一样」,所以原串的某个后缀是回文,等价于这个后缀等于它自己的反转,也就是等于原串反转后的对应前缀。而我们构造的combined字符串,通过前缀函数找到的最长公共前后缀,刚好就是原串后缀和反转串前缀的最长匹配——这个匹配的长度,就是最长回文后缀的长度。
整个过程的时间复杂度是O(n),因为前缀函数的计算是线性的,拼接后的字符串长度是2n+1,完全符合你要的优于O(n²)的要求。
内容的提问来源于stack exchange,提问作者Ignos




