题目链接
题目描述
给你一个由 'T' 和 'F' 字符组成的字符串 answerKey
和一个整数 k
,你需要将最多 k
个字符进行翻转(将 'T' 翻转成 'F' 或者将 'F' 翻转成 'T'),返回可以得到的最长连续相同字符子串的长度。
解法一:滑动窗口 + 辅助函数法
核心思路
通过滑动窗口技术,分别计算以'T'为目标和以'F'为目标的最长连续子串长度,取两者的最大值。定义辅助函数计算以特定字符为目标时的最长子串长度,窗口内维护不匹配目标字符的数量不超过k。
代码实现
class Solution:
def maxConsecutiveAnswers(self, answerKey: str, k: int) -> int:
n = len(answerKey)
def maxConsecutive(expect: str) -> int:
l = ans = cnt = 0
for i in range(n):
# 统计窗口内不匹配expect的字符数量
cnt += answerKey[i] != expect
# 当不匹配数量超过k时,移动左指针
while cnt > k:
cnt -= answerKey[l] != expect
l += 1
# 更新最长连续长度
ans = max(ans, i - l + 1)
return ans
# 分别计算以'T'和'F'为目标的最长长度,取最大值
return max(maxConsecutive('T'), maxConsecutive('F'))
代码解析
-
辅助函数设计:
maxConsecutive
函数接收目标字符expect
('T' 或 'F')l
为滑动窗口左指针,ans
记录最长长度,cnt
记录不匹配expect
的字符数
-
窗口维护逻辑:
- 右指针遍历字符串,不匹配目标字符时
cnt
加1 - 当
cnt > k
时移动左指针,减少左边界不匹配字符计数 - 确保窗口内最多有k个字符需要翻转
- 右指针遍历字符串,不匹配目标字符时
-
结果计算:
- 分别计算以'T'和'F'为目标的最长子串长度
- 返回两者最大值,即为可得到的最长连续相同字符子串长度
复杂度分析
- 时间复杂度:O(n),每个字符被访问两次(两次辅助函数遍历)
- 空间复杂度:O(1),仅使用常数级额外空间
解法二:滑动窗口 + 双计数器法
核心思路
使用滑动窗口同时跟踪窗口内'T'和'F'的数量,当两者数量都超过k时移动左指针,确保窗口内至少存在一种字符,其翻转次数不超过k次即可得到连续相同字符子串。
代码实现
class Solution:
def maxConsecutiveAnswers(self, answerKey: str, k: int) -> int:
l = ans = cnt_t = cnt_f = 0
for i in range(len(answerKey)):
if answerKey[i] == 'T':
cnt_t += 1 # 当前字符是'T',更新'T'的数量
else:
cnt_f += 1 # 当前字符是'F',更新'F'的数量
# 当'T'和'F'的数量都超过k时,需要缩小窗口
while cnt_t > k and cnt_f > k:
if answerKey[l] == 'T':
cnt_t -= 1 # 左边界是'T',减少'T'的数量
else:
cnt_f -= 1 # 左边界是'F',减少'F'的数量
l += 1 # 左指针右移,缩小窗口范围
ans = max(ans, i - l + 1) # 更新最长连续长度
return ans
代码解析
-
变量定义:
l
:滑动窗口左指针,初始化为0ans
:记录最长连续子串长度,初始化为0cnt_t
/cnt_f
:分别记录窗口内'T'和'F'的数量,初始化为0
-
窗口维护逻辑:
- 右指针遍历字符串,根据当前字符更新对应计数
- 当
cnt_t > k
且cnt_f > k
时,说明:- 若全转为'T'需翻转
cnt_f
次(超过k) - 若全转为'F'需翻转
cnt_t
次(超过k)
- 若全转为'T'需翻转
- 移动左指针并更新边界字符计数,直到至少一种翻转次数≤k
-
关键逻辑说明:
- 窗口内始终满足:
min(cnt_t, cnt_f) ≤ k
- 此时必存在一种字符,翻转次数≤k:
- 若
cnt_f ≤ k
,可将所有'F'转为'T'(翻转cnt_f
次) - 若
cnt_t ≤ k
,可将所有'T'转为'F'(翻转cnt_t
次)
- 若
- 窗口内始终满足:
-
长度计算:
- 窗口长度为
i - l + 1
(左闭右闭区间) - 该长度即为翻转不超过k次可得到的最长连续子串长度
- 窗口长度为
复杂度分析
- 时间复杂度:O(n),每个字符最多被左右指针各访问一次
- 空间复杂度:O(1),仅使用常数级额外空间
两种解法对比
维度 | 解法一(辅助函数法) | 解法二(双计数器法) |
---|---|---|
核心思想 | 分目标计算最长子串 | 同时跟踪两种字符数量 |
代码结构 | 模块化设计,辅助函数职责明确 | 单函数实现,代码更紧凑 |
逻辑重点 | 维护单目标不匹配数≤k | 维护双目标不匹配数不同时>k |
遍历次数 | 两次遍历(T和F各一次) | 一次遍历 |
适用场景 | 适合初学者理解滑动窗口应用 | 适合追求代码简洁性的场景 |
示例详解
以输入answerKey = "TFTF", k = 1
为例:
解法二执行过程:
i=0
,字符'T',cnt_t=1
,窗口长度1,ans=1
i=1
,字符'F',cnt_f=1
,窗口长度2,ans=2
i=2
,字符'T',cnt_t=2
,此时cnt_t=2 > k=1
且cnt_f=1 ≤ k=1
,不缩窗,窗口长度3,ans=3
- 因为
cnt_f=1 ≤ k=1
,可将'F'转为'T'(翻转1次),得到连续'T'子串
- 因为
i=3
,字符'F',cnt_f=2
,此时cnt_t=2 > 1
且cnt_f=2 > 1
,进入缩窗:- 左指针
l=0
,字符'T',cnt_t=1
,l=1
- 现在
cnt_t=1 ≤ 1
且cnt_f=2 > 1
,退出缩窗 - 窗口长度
3-1+1=3
,ans=3
- 左指针
- 最终返回3,即最长子串为"FTF"或"TFT"(翻转1次可得连续字符)
总结
两种解法均采用滑动窗口技术,时间空间复杂度相同,是解决该问题的高效方案:
- 解法一通过辅助函数分目标计算,逻辑清晰,适合教学理解
- 解法二通过双计数器一次遍历,代码简洁,适合工程实现
滑动窗口的核心在于维护窗口内约束条件(本题中为翻转次数≤k),通过左右指针动态调整窗口范围,确保在O(n)时间内找到最优解。该思路可扩展至类似的"最多修改k个元素"的子串问题,是算法设计中的经典技巧。