image.png

思路1:贪心算法

  • 一段区间内部,要是刚好有LC2024.考试的最大困扰 - 图2T,那么就把所有的T统统变成F,那么这一段区间就是一段连续的FFF...FFF序列了。如果能把所有的“有LC2024.考试的最大困扰 - 图3T的区间”全部进行这样的操作,那么就可以得到最好的结果了。
  • 具体的做法如下,listT[i]表示第LC2024.考试的最大困扰 - 图4个出现的T的下标,那么第LC2024.考试的最大困扰 - 图5T出现的位置,减去第LC2024.考试的最大困扰 - 图6T出现的位置,得到的区间就是我们希望的“有LC2024.考试的最大困扰 - 图7T的区间”。
  • 但是害怕一种情况的出现,就是形如FFFFFTTT and k = 3这样的情况,这样的话第0个T所在的位置太靠后了,前面那么多个F算不到里面去了,所以,在listT[i]中预先加上LC2024.考试的最大困扰 - 图8,等于在AnswerKeyLC2024.考试的最大困扰 - 图9位置预先隐藏一个字符,这样计算listT[i + k] - listT[i - 1] - 1就能计算出完整情况了。

    代码1:

  1. class Solution:
  2. def maxConsecutiveAnswers(self, answerKey: str, k: int) -> int:
  3. # 贪心算法去做一次扫描估计是不行的,里面重复的情况太复杂了
  4. # 考虑另外一种贪心算法
  5. listT, listF = [-1], [-1]
  6. lenAnswerKey = len(answerKey)
  7. for i in range(lenAnswerKey):
  8. if answerKey[i] == 'T':
  9. # 存放出现位置的下标
  10. listT.append(i)
  11. else:
  12. listF.append(i)
  13. # 认为最后增加一个位置是二者中的任意一个
  14. listT.append(lenAnswerKey)
  15. listF.append(lenAnswerKey)
  16. if k >= len(listT) - 2 or k >= len(listF) - 2:
  17. return lenAnswerKey
  18. res = 0
  19. i = 1
  20. # 实际上在算F序列的大小
  21. while i + k < len(listT):
  22. res = max(res, listT[i + k] - listT[i - 1] - 1)
  23. i += 1
  24. i = 1
  25. while i + k < len(listF):
  26. res = max(res, listF[i + k] - listF[i - 1] - 1)
  27. i += 1
  28. return res

思路2:滑动窗口

  • 基本的思路也用到了贪心的思想:如果某个区间内有LC2024.考试的最大困扰 - 图10F,那么把LC2024.考试的最大困扰 - 图11F全部变成T,就可以得到一组最长的区间。现在的任务变成了如何找到符合这一条件的区间。
  • 利用滑动窗口的思想,如果符合要求就往右走,不符合要求就重复弹出左边的元素。直到满足要求为止。
  • right += 1靠后写,不然计算maxLen = max(maxLen, right - left + 1)会多算。

    代码2:

    1. class Solution:
    2. def maxConsecutiveAnswers(self, answerKey: str, k: int) -> int:
    3. # 滑动窗口
    4. # 如果没有满k个F,就继续扫,如果满k个F了,就放弃掉最左边的数字
    5. def scanMax(ch: str):
    6. left, right = 0, 0
    7. maxLen, countK = 0, 0
    8. while right < len(answerKey):
    9. countK += (1 if answerKey[right] == ch else 0)
    10. while countK > k:
    11. countK -= (1 if answerKey[left] == ch else 0)
    12. left += 1
    13. maxLen = max(maxLen, right - left + 1)
    14. # 写在后面,不然会多加
    15. right += 1
    16. return maxLen
    17. return max(scanMax('T'), scanMax('F'))

    思路3:二分查找

  • 采用类似前缀和的方法进行计数,cnt[i]表示长度为len的字符串中,T出现的次数。

  • 利用二分查找代替暴力搜索,如果T或者F出现的次数小于等于k,那么这个子字符串一定是符合的,然后通过长度去遍历,得到满足条件的最长长度即可。

    代码:

    1. class Solution {
    2. public:
    3. //查看该长度是否是满足的
    4. int judge(int len, vector<int> cnt, int n, int k) {
    5. // i需要从0开始,如果从1开始会忽略掉第一个
    6. for (int i = 0; i + len <= n; i++) {
    7. int cur_t = cnt[i + len] - cnt[i];
    8. if (min(cur_t, len - cur_t) <= k) {
    9. return true;
    10. }
    11. }
    12. return false;
    13. }
    14. int maxConsecutiveAnswers(string answerKey, int k) {
    15. int n = answerKey.size();
    16. vector<int> cnt(n + 1, 0);
    17. for (int i = 1; i <= n; i++) {
    18. // cnt[i]长度为i的数组中T出现的频率
    19. cnt[i] = cnt[i - 1] + (answerKey[i - 1] == 'T' ? 1 : 0);
    20. }
    21. //这里的mid的意义是长度
    22. int left = 1, right = n;
    23. while (left < right) {
    24. int mid = (left + right + 1) / 2;
    25. if (judge(mid, cnt, n, k)) {
    26. left = mid;
    27. } else {
    28. right = mid - 1;
    29. }
    30. }
    31. return right;
    32. }
    33. };