0
点赞
收藏
分享

微信扫一扫

LeetCode——2024.考试的最大困扰度

IT影子 2022-05-03 阅读 59

2024. 考试的最大困扰度 - 力扣(LeetCode) (leetcode-cn.com)

【考察点】前缀和+二分、滑动窗口

方法1:前缀和+二分

为什么会想到用二分做?

从题目的表述来看,在添加k次操作后,最长连续字符的结果一定位于区间 [ m , n ] [m, n] [m,n](其中, m m m表示原先字符串中连续字符的最大长度, n n n表示字符串长度)

从结果的单调性质不难想到:针对具有单调性的区间,利用二分求出结果

思路

既然已经确定利用二分的思想枚举最终解

现在的问题就在于:

如何验证解的正确性?

首先,需要进行前缀和预处理操作

简单而言,前缀和数组s[i]表示前i个字符中”T”/”F”的数量

接下来,便是遍历整个数组

并以当前的遍历点 i i i作为右端点,0为左端点,二分出可能值 x x x(即在经过不超过 k k k次操作后,可能的最长连续字符长度),则x具有的性质如下:

  1. x尽可能离右端点越远越好(代表连续字符更长)
  2. 同时需要满足以 s [ x ] + k ≥ s [ i ] s[x]+k ≥ s[i] s[x]+ks[i](表示中间的字符能够通过不超过 k k k次的转换变成相匹配字符,如从”T”变成”F”)

在二分结束后,最长的连续字符长度就是 i − l + 1 i - l + 1 il+1 i i i为当前遍历点,即固定的右端点, l l l表示遍历后距离i最远且满足不超过 k k k次操作的位置)

【需要注意的是】,对于T和F需要分别进行上述操作

实现

class Solution {
public:
    bool check(int x, int k, int a[], int sum) {
        if(a[x] + k >= sum) return true;
        else return false;
    }

    int maxConsecutiveAnswers(string a, int k) {
        int n = a.size();
        int sum_T[n+1], sum_F[n+1];

        memset(sum_T, 0, sizeof(sum_T));
        memset(sum_F, 0, sizeof(sum_F));

        int ans = 0;
        for(int i = 1; i <= n; i++) {
						// 前缀和数组
            sum_T[i] = sum_T[i-1] + (a[i-1] == 'T');
            sum_F[i] = sum_F[i-1] + (a[i-1] == 'F');
						// 分别二分T和F
            int l = 0, r = i - 1;
            while(l < r) {
                int mid = (l + r) >> 1;

                if(check(mid, k, sum_T, sum_T[i]))  r = mid;
                else l = mid + 1;
            }
            ans = max(ans, i - l); // 需要注意的细节是,数组是从1开始的,因此这里就是(i-1)-l+1
            
            l = 0, r = i - 1;
            while(l < r) {
                int mid = (l + r) >> 1;

                if(check(mid, k, sum_F, sum_F[i]))  r = mid;
                else l = mid + 1;
            }
            ans = max(ans, i - l);
        }

        return ans;
    }
};

算法复杂度分析

对于遍历一遍字符数组,复杂度是 O ( n O(n O(n)

同时,每个循环内部,需要二分从0~i的区间,复杂度是 O ( l o g n ) O(logn) O(logn)

综上,复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

方法2:滑动窗口

借助滑动窗口的思想,找到满足条件的最大值

具体而言:

  • 首先,维护一个右端点,以及一个中间变量 s u m sum sum以保存从左端点到右端点中不同字符的个数
  • s u m > k sum>k sum>k时,说明当前维护的区间无法满足在 k k k次操作的情况下变为同一字符
  • 进而,挪动左端点,并相应的修改 s u m sum sum的值,直到 s u m ≤ k sum≤k sumk

下面通过一个实例体会一下滑动窗口是如何实现的?
在这里插入图片描述

实现

class Solution {
public:
    int maxConsecutiveAnswers(string a, int k) {
        int n = a.size();
        int ans = 0;

        int t = 0;
        for(int r = 0, l = 0; r < n; r++) {
            t += a[r] == 'F';

            while(t > k) {
                if(a[l++] == 'F')   t--;
            }
            ans = max(ans, r - l + 1);
        }

        t = 0;
        for(int r = 0, l = 0; r < n; r++) {
            t += a[r] == 'T';

            while(t > k) {
                if(a[l++] == 'T')   t--;
            }
            ans = max(ans, r - l + 1);
        }

        return ans;
    }
};

复杂度分析

只需要遍历该字符串一遍,时间算法复杂度为 O ( n ) O(n) O(n)

举报

相关推荐

0 条评论