424. 替换后的最长重复字符
424. 替换后的最长重复字符
难度中等
给你一个仅由大写英文字母组成的字符串,你可以将任意位置上的字符替换成另外的字符,总共可最多替换 k 次。在执行上述操作后,找到包含重复字母的最长子串的长度。
**注意:**字符串长度 和 k 不会超过 10^4。
示例 1:
输入:s = "ABAB", k = 2
输出:4
解释:用两个'A'替换为两个'B',反之亦然。
示例 2:
输入:s = "AABABBA", k = 1
输出:4
解释:
将中间的一个'A'替换为'B',字符串变为 "AABBBBA"。
子串 "BBBB" 有最长重复字母, 答案为 4。
函数签名:
func characterReplacement(s string, k int) int
分析
先尝试朴素解。穷举每个子串,看看每个子串在允许替换 k 个字符的情况下能否变成全部字符相同的串,可以通过统计得到最多字符的个数,这个个数加上 k 如果大于等于对应子串的长度,就可以用子串的长度更新答案。
func characterReplacement(s string, k int) int {
n := len(s)
if n == 0 {
return 0
}
res := 0
for i := 0; i < n-1; i++ {
for j := n-1; j >= i; j-- {
if check(s[i:j+1], k) {
res = max(res, j-i+1)
break
}
}
}
return res
}
func check(sub string, k int) bool {
memo := [26]int{}
maxCnt := 0
for _, v := range sub {
memo[v-'A']++
maxCnt = max(maxCnt, memo[v-'A'])
}
return maxCnt + k >= len(sub)
}
时间复杂度是O(n^3)
。
滑动窗口
朴素解法超时,必须考虑一个复杂度更低的实现。
继承一部分朴素解法的思想,用两个指针来维护子串,而不是用内外循环的方法得到子串。
两个指针形成一个窗口,每次统计窗口中最多的元素个数,如果其加上 k 的值不小于窗口长度,则窗口长度就可以更新结果。
func characterReplacement(s string, k int) int {
n := len(s)
res := 0
cnt := make([]int, 26)
for left, right := 0, 0; right < n; right++ {
cnt[s[right]-'A']++
maxCnt := calMax(cnt)
if maxCnt+k >= right-left+1 {
res = max(res, right-left+1)
} else {
cnt[s[left]-'A']--
left++
}
}
return res
}
func calMax(cnt []int) int {
res := 0
for _, v := range cnt {
res = max(res, v)
}
return res
}
注意到用了一个 cnt 数组来记录窗口里边各个字母的个数;借助这个数组来遍历计算窗口中最多的字母的个数;因为字母只有 26 个,这么做的复杂度可以接受。
实际上这个地方还可以优化。
只需要加一个变量 maxCnt 来维护窗口里的最多字符数量,calMax 函数可以省去。在窗口右边界右移的时候更新 maxCnt, 但是在窗口左边界向右移动的地方,不用更新 maxCnt —— 这样并不影响结果:
左边界向右移动一个位置的时候,maxCnt 或者不变,或者值减 1。 当左边界向右移动之前,如果有多种字符长度相等,左边界向右移动不改变 maxCnt 的值。例如 s = [AAABBB]、k = 2,左边界 A 移除以后,窗口内字符出现次数不变,依然为 3; 当左边界移除以后,使得此时 maxCnt 的值变小,接下来继续让右边界向右移动一格,有两种情况:① 右边界如果读到了刚才移出左边界的字符,恰好 maxCnt 的值被正确维护;② 由于最终要找的只是最长替换 k 次以后重复子串的长度,右边界如果读到了不是刚才移出左边界的字符,新的子串要想在符合题意的条件下变得更长,maxCnt 一定要比之前的值还更大,因此不会错过更优的解。
func characterReplacement(s string, k int) int {
n := len(s)
res := 0
cnt := make([]int, 26)
maxCnt := 0
for left, right := 0, 0; right < n; right++ {
cnt[s[right]-'A']++
maxCnt = max(maxCnt, cnt[s[right]-'A'])
if maxCnt+k >= right-left+1 {
res = max(res, right-left+1)
} else {
cnt[s[left]-'A']--
// 这里无需更新 maxCnt,不影响结果
left++
}
}
return res
}
时间复杂度 O(n)
,空间复杂度O(26) = O(1)
。