LeetCode 424 - 替换后的最长重复字符


文章目录
- 摘要
- 描述
- 题解答案
- 题解代码分析
- 代码解析
- 示例测试及结果
- 时间复杂度
- 空间复杂度
- 总结
摘要
这道题《LeetCode 424. 替换后的最长重复字符》属于典型的滑动窗口题目,同时也考验我们对字符串区间内特征值的理解。题目要求在最多替换 k 次字符的前提下,找到最长的一段“重复字符子串”。
虽然描述看起来有点拗口,但它本质上是在问:
在一个窗口中,如果我们把最多 k 个字符替换成某一个字符,窗口能达到的最大长度是多少?
这个问题在日志分析、字符串纠错、甚至简单压缩算法中都有类似逻辑,实际应用还挺广的。

描述
给定一个由大写英文字母组成的字符串 s,我们可以最多执行 k 次替换操作,每次可将任意字符替换成任意大写英文字母。目标是得到一个最长的子串,这个子串的所有字符最终都一样。
举个例子:
s = "ABAB", k = 2,我们可以把两个 A 都改成 B,也可以反过来,把两个 B 改成 A。
最长的重复子串长度是 4。
再比如:
s = "AABABBA", k = 1,通过把中间一个 A 改成 B,我们能得到最长重复子串 BBBB,长度是 4。
这个题的关键点不是“具体替换成什么”,而是“窗口中最大相同字母数量和窗口长度的关系”。
题解答案
最佳做法是使用滑动窗口,并且窗口是不断向右扩张的,同时控制窗口是否可行。
核心判断条件是:
窗口长度 - 窗口内出现次数最多的字符数量 ≤ k
只要满足这个条件,窗口就是合法的,因为我们最多只需要替换 k 个字符,就能把整个窗口变成一种字符。
这里有一个非常关键的技巧:
窗口内“出现次数最多的字符数量”不需要准确更新,也不需要在窗口缩小时同步减少。
只要保持它是当前出现过的最大值即可(这个值是单调不减的)。
实际验证时完全成立,会让整个算法线性化。

题解代码分析
下面是完整可运行的 Swift 版本:
import Foundationclass Solution {func characterReplacement(_ s: String, _ k: Int) -> Int {let chars = Array(s)var freq = [Int](repeating: 0, count: 26)var left = 0var maxCount = 0 // 窗口内出现最多的字符次数var result = 0for right in 0..<chars.count {let index = Int(chars[right].asciiValue! - Character("A").asciiValue!)freq[index] += 1maxCount = max(maxCount, freq[index])// 如果窗口不合法,则收缩while (right - left + 1) - maxCount > k {let leftIndex = Int(chars[left].asciiValue! - Character("A").asciiValue!)freq[leftIndex] -= 1left += 1}result = max(result, right - left + 1)}return result}
}// Demo 测试
let s = Solution()
print("测试1:", s.characterReplacement("ABAB", 2)) // 4
print("测试2:", s.characterReplacement("AABABBA", 1)) // 4
代码解析
整个逻辑分成几块:
-
字符数组
将字符串转成数组能提升访问效率,同时方便根据 ASCII 做计数。 -
频率数组
freq
用 26 长度的数组存每个字母的出现次数。
因为只有大写字母,不需要用哈希表。 -
滑动窗口左右边界
right一直向右移动,而left在窗口非法时向右缩小窗口。 -
maxCount 的意义
- 在窗口内出现最多的字符出现了 maxCount 次
- 如果窗口长度比 maxCount 大太多,就说明需要替换的字符数太多
- 超过 k 时必须移动 left
注意:maxCount 不会减少,这个行为是故意的,它保证 O(n) 运行。
-
每次更新 result
当前窗口长度就是一个可能的答案。
整体代码虽然不算短,但结构非常稳健,非常适用于大规模字符串处理。
示例测试及结果
测试 1:
输入: s = "ABAB", k = 2
输出: 4
解释:
把两个 A 换成 B,或反过来,把两个 B 换成 A,都能得到 “AAAA” 或 “BBBB”。
测试 2:
输入: s = "AABABBA", k = 1
输出: 4
执行 Demo 后输出:
测试1: 4
测试2: 4
可以看到结果完全一致,这也验证了滑动窗口的正确性。
时间复杂度
O(n)
整个字符串只被右指针遍历一遍,左指针最多走 n 次,没有回头的操作。
频率计算与判断操作都是 O(1)。
对于长度 10⁵ 的字符串,这个性能非常友好。
空间复杂度
O(1)
频率数组固定 26 个元素,额外变量常量级,空间不随输入规模变化。
总结
这道题乍一看有点像动态规划或者暴力枚举,但真正的最优解其实是滑动窗口。
关键思想在于判断窗口可行性的条件:窗口长度与最大频率的关系。
理解了 “窗口长度 - 最大频率 ≤ k” 这个公式,就等于是掌握了整道题。
实际开发中,如果你需要做:
- 日志文本清洗
- 文本纠错系统
- 简单压缩编码优化
- 某类统计窗口中连续字符结构的业务
这一类问题都能用到类似的窗口思想。
