最长回文子串的变种——寻找最长“近似”回文子串,最多允许k次字符替换
题目描述:
给定一个字符串s和一个整数k,你可以替换字符串s中最多k个字符(即将某个字符改为任意其他字符)。目标是:在最多替换k个字符的前提下,找到最长的子串,使得这个子串是回文子串。换句话说,我们希望找到一个子串s[i:j],在最多修改k个字符后,它能变成回文串。求这个最长子串的长度。
示例:
输入:s = "abca", k = 2
输出:4
解释:整个字符串"abca",可以修改字符'b'为'c',字符'c'为'a',得到"acca",这是一个回文串。因此最长长度为4。
解题过程:
这个问题的核心是:对于一个给定的子串,判断它是否能在最多k次字符替换后变成回文串。如果能,那么该子串就是一个候选。我们需要找到最长的这样的子串。
-
问题转化
对于任意子串s[l:r](索引从l到r,包含两端),长度为len = r - l + 1。我们希望知道:将这个子串变成回文串,最少需要修改多少个字符。
如何计算最少修改次数?
回文要求s[l+i]与s[r-i]相等(i从0到中间)。因此,我们只需要考虑这些对称位置上的字符对:- 如果s[l+i] == s[r-i],则该对已经匹配,不需要修改。
- 如果s[l+i] != s[r-i],则至少要修改其中一个字符才能匹配。但注意,一次修改可以同时“修复”一对字符(比如将s[l+i]改成s[r-i],或者相反)。因此,每一对不匹配的字符对,最少需要1次修改。
所以,对于子串s[l:r],最少修改次数 = 子串中不匹配的字符对数。
更准确地说,我们检查所有对称位置,统计不匹配的对数,记为diff。那么最少需要修改的次数就是diff(因为每次修改可以修正一对不匹配)。
-
判断条件
如果对于子串s[l:r],有 diff <= k,那么这个子串可以在最多k次修改后变成回文串。
注意:这里diff表示最少修改次数,因为我们每次修改可以同时让一对字符匹配,所以不需要除以2。
另外,如果子串长度为奇数,最中间的那个字符可以任意,不会影响diff的统计(因为对称比较时不会涉及它)。 -
暴力法思路(低效)
最简单的想法是枚举所有子串(O(n^2)个子串),对每个子串计算diff(O(n)时间),然后判断diff <= k,并记录最大长度。总复杂度O(n^3),对于较长的字符串会超时。 -
优化思路
我们希望将复杂度降到O(n^2)或更好。
注意到:对于固定的子串中心,我们可以利用双指针扩展的方式,在扩展过程中动态更新diff。
回文子串有奇数和偶数长度两种情况,我们可以分别以每个位置(或每两个位置之间)作为中心,向外扩展。 -
中心扩展法
我们遍历每个可能的中心:- 奇数长度回文:中心为单个字符,初始左右指针l = r = i。
- 偶数长度回文:中心为两个字符之间,初始l = i, r = i+1。
然后,我们同时向外移动左右指针,并维护当前子串的不匹配对数diff。
每次移动时,比较s[l]和s[r]: - 如果s[l] != s[r],则diff加1。
- 如果s[l] == s[r],diff不变。
如果diff <= k,说明当前子串满足条件,更新最大长度。
如果diff > k,则停止扩展(因为继续扩展diff不会减少)。
这样,每个中心扩展的复杂度是O(n),总复杂度O(n^2)。
但注意:当中心固定时,向外扩展过程中,子串长度增加,不匹配对数diff只会增加(或保持不变),不会减少。因此一旦diff超过k,更长的子串肯定不满足条件,可以停止。
-
实现细节
伪代码如下:max_len = 0 n = len(s) for center in range(2*n-1): 如果center是偶数,对应奇数长度回文,l = center//2, r = center//2 如果center是奇数,对应偶数长度回文,l = center//2, r = center//2 + 1 diff = 0 while l >= 0 and r < n: 如果 s[l] != s[r]: diff += 1 如果 diff <= k: 更新 max_len = max(max_len, r - l + 1) l -= 1 r += 1注意:当l和r超出字符串边界时停止。
-
复杂度
时间复杂度:O(n^2)。因为每个中心最多扩展O(n)次,总共有2n-1个中心。
空间复杂度:O(1)。 -
举例验证
以s = "abca", k = 2为例:- 中心0(奇数):l=0, r=0,子串"a",diff=0,长度1
扩展:l=-1停止 - 中心1(偶数):l=0, r=1,子串"ab",s[0]!=s[1],diff=1,长度2
扩展:l=-1停止 - 中心2(奇数):l=1, r=1,子串"b",diff=0,长度1
扩展:l=0, r=2,子串"abc",s[0]!=s[2](a!=c),diff=1,长度3
扩展:l=-1停止 - 中心3(偶数):l=1, r=2,子串"bc",s[1]!=s[2](b!=c),diff=1,长度2
扩展:l=0, r=3,子串"abca",s[0]!=s[3]? 这里s[0]=a, s[3]=a,相等,所以diff不变仍为1。长度4
此时diff=1 <= k=2,所以更新max_len=4
继续扩展:l=-1停止 - 中心4(奇数):l=2, r=2,子串"c",diff=0,长度1
扩展:l=1, r=3,子串"bca",s[1]!=s[3](b!=a),diff=1,长度3
扩展:l=0, r=4超出边界停止 - 中心5(偶数):l=2, r=3,子串"ca",s[2]!=s[3](c!=a),diff=1,长度2
扩展:l=1, r=4超出边界停止 - 中心6(奇数):l=3, r=3,子串"a",diff=0,长度1
扩展:l=2, r=4超出边界停止
最终得到max_len=4。
- 中心0(奇数):l=0, r=0,子串"a",diff=0,长度1
-
思考扩展
本题的关键在于发现“最少修改次数等于不匹配的字符对数”,并且利用中心扩展动态维护不匹配对数。这种方法在k较小时尤其有效,但即使k很大,复杂度也仅为O(n^2)。