LeetCode 2024. 考试的最大困扰度

4 阅读5分钟

题目链接

1798. 最大化考试的困惑度

题目描述

给你一个由 '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'))

代码解析

  1. 辅助函数设计

    • maxConsecutive 函数接收目标字符 expect('T' 或 'F')
    • l 为滑动窗口左指针,ans 记录最长长度,cnt 记录不匹配 expect 的字符数
  2. 窗口维护逻辑

    • 右指针遍历字符串,不匹配目标字符时 cnt 加1
    • cnt > k 时移动左指针,减少左边界不匹配字符计数
    • 确保窗口内最多有k个字符需要翻转
  3. 结果计算

    • 分别计算以'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

代码解析

  1. 变量定义

    • l:滑动窗口左指针,初始化为0
    • ans:记录最长连续子串长度,初始化为0
    • cnt_t/cnt_f:分别记录窗口内'T'和'F'的数量,初始化为0
  2. 窗口维护逻辑

    • 右指针遍历字符串,根据当前字符更新对应计数
    • cnt_t > kcnt_f > k时,说明:
      • 若全转为'T'需翻转cnt_f次(超过k)
      • 若全转为'F'需翻转cnt_t次(超过k)
    • 移动左指针并更新边界字符计数,直到至少一种翻转次数≤k
  3. 关键逻辑说明

    • 窗口内始终满足:min(cnt_t, cnt_f) ≤ k
    • 此时必存在一种字符,翻转次数≤k:
      • cnt_f ≤ k,可将所有'F'转为'T'(翻转cnt_f次)
      • cnt_t ≤ k,可将所有'T'转为'F'(翻转cnt_t次)
  4. 长度计算

    • 窗口长度为i - l + 1(左闭右闭区间)
    • 该长度即为翻转不超过k次可得到的最长连续子串长度

复杂度分析

  • 时间复杂度:O(n),每个字符最多被左右指针各访问一次
  • 空间复杂度:O(1),仅使用常数级额外空间

两种解法对比

维度解法一(辅助函数法)解法二(双计数器法)
核心思想分目标计算最长子串同时跟踪两种字符数量
代码结构模块化设计,辅助函数职责明确单函数实现,代码更紧凑
逻辑重点维护单目标不匹配数≤k维护双目标不匹配数不同时>k
遍历次数两次遍历(T和F各一次)一次遍历
适用场景适合初学者理解滑动窗口应用适合追求代码简洁性的场景

示例详解

以输入answerKey = "TFTF", k = 1为例:

解法二执行过程:

  1. i=0,字符'T',cnt_t=1,窗口长度1,ans=1
  2. i=1,字符'F',cnt_f=1,窗口长度2,ans=2
  3. i=2,字符'T',cnt_t=2,此时cnt_t=2 > k=1cnt_f=1 ≤ k=1,不缩窗,窗口长度3,ans=3
    • 因为cnt_f=1 ≤ k=1,可将'F'转为'T'(翻转1次),得到连续'T'子串
  4. i=3,字符'F',cnt_f=2,此时cnt_t=2 > 1cnt_f=2 > 1,进入缩窗:
    • 左指针l=0,字符'T',cnt_t=1l=1
    • 现在cnt_t=1 ≤ 1cnt_f=2 > 1,退出缩窗
    • 窗口长度3-1+1=3ans=3
  5. 最终返回3,即最长子串为"FTF"或"TFT"(翻转1次可得连续字符)

总结

两种解法均采用滑动窗口技术,时间空间复杂度相同,是解决该问题的高效方案:

  1. 解法一通过辅助函数分目标计算,逻辑清晰,适合教学理解
  2. 解法二通过双计数器一次遍历,代码简洁,适合工程实现

滑动窗口的核心在于维护窗口内约束条件(本题中为翻转次数≤k),通过左右指针动态调整窗口范围,确保在O(n)时间内找到最优解。该思路可扩展至类似的"最多修改k个元素"的子串问题,是算法设计中的经典技巧。