考试的最大困扰度 图解题解
这道题到底在问什么
- 输入
- answerKey="TTFTTFTT", k=1
- 输出
- 5
- 输入
- answerKey="TTFF", k=2
- 输出
- 4
- 输入
- answerKey="TFFT", k=0
- 输出
- 2
最优解:一步一步想明白
- 3记牢这一句:窗口里少数派的个数就是要翻转的次数,这个次数不超过 k,窗口就合法。少数派超了就从左边缩,缩到刚好合法为止。下面每一帧都在套这句话。
- 4先把整串摆出来,下标从 0 到 7 依次是 T T F T T F T T。我们要从左往右滑一个窗口,窗口里数着 T 和 F 各多少个。右边这张小面板就是窗口内的统计,现在窗口还没开张,两个计数都是 0。开始读入字符。
- 5把右端下标 0 的字符 T 读进窗口,它的计数加一。现在窗口里 T 有 1 个,F 有 0 个,少数派是 0 个。少数派 0 ≤ k = 1,窗口还合法,先放着,等下结算。
- 6结算这一步。窗口 [0..0] 现在合法,大小是 1,里面翻掉少数派的 0 个字符就能全部相同。它比之前见过的都长,刷新历史最长 ans = 1。
- 7把右端下标 1 的字符 T 读进窗口,它的计数加一。现在窗口里 T 有 2 个,F 有 0 个,少数派是 0 个。少数派 0 ≤ k = 1,窗口还合法,先放着,等下结算。
- 8结算这一步。窗口 [0..1] 现在合法,大小是 2,里面翻掉少数派的 0 个字符就能全部相同。它比之前见过的都长,刷新历史最长 ans = 2。
- 9把右端下标 2 的字符 F 读进窗口,它的计数加一。现在窗口里 T 有 2 个,F 有 1 个,少数派是 1 个。少数派 1 ≤ k = 1,窗口还合法,先放着,等下结算。
- 10结算这一步。窗口 [0..2] 现在合法,大小是 3,里面翻掉少数派的 1 个字符就能全部相同。它比之前见过的都长,刷新历史最长 ans = 3。
- 11把右端下标 3 的字符 T 读进窗口,它的计数加一。现在窗口里 T 有 3 个,F 有 1 个,少数派是 1 个。少数派 1 ≤ k = 1,窗口还合法,先放着,等下结算。
- 12结算这一步。窗口 [0..3] 现在合法,大小是 4,里面翻掉少数派的 1 个字符就能全部相同。它比之前见过的都长,刷新历史最长 ans = 4。
- 13把右端下标 4 的字符 T 读进窗口,它的计数加一。现在窗口里 T 有 4 个,F 有 1 个,少数派是 1 个。少数派 1 ≤ k = 1,窗口还合法,先放着,等下结算。
- 14结算这一步。窗口 [0..4] 现在合法,大小是 5,里面翻掉少数派的 1 个字符就能全部相同。它比之前见过的都长,刷新历史最长 ans = 5。
- 15把右端下标 5 的字符 F 读进窗口,它的计数加一。现在窗口里 T 有 4 个,F 有 2 个,少数派是 2 个。少数派 2 已经 > k = 1,窗口暂时不合法,得从左边缩一缩。
- 16窗口不合法,从最左边把下标 0 的 T 踢出去,左指针右移到 1。现在窗口里 T 有 3 个,F 有 2 个,少数派 2 个。少数派还是 2 个,仍然 > k = 1,继续往右踢。
- 17窗口不合法,从最左边把下标 1 的 T 踢出去,左指针右移到 2。现在窗口里 T 有 2 个,F 有 2 个,少数派 2 个。少数派还是 2 个,仍然 > k = 1,继续往右踢。
- 18窗口不合法,从最左边把下标 2 的 F 踢出去,左指针右移到 3。现在窗口里 T 有 2 个,F 有 1 个,少数派 1 个。少数派 1 ≤ k = 1,终于合法了,可以停下来结算。
- 19结算这一步。窗口 [3..5] 现在合法,大小是 3,里面翻掉少数派的 1 个字符就能全部相同。它没有超过历史最长 5,ans 保持不动,窗口继续往右滑。
- 20把右端下标 6 的字符 T 读进窗口,它的计数加一。现在窗口里 T 有 3 个,F 有 1 个,少数派是 1 个。少数派 1 ≤ k = 1,窗口还合法,先放着,等下结算。
- 21结算这一步。窗口 [3..6] 现在合法,大小是 4,里面翻掉少数派的 1 个字符就能全部相同。它没有超过历史最长 5,ans 保持不动,窗口继续往右滑。
- 22把右端下标 7 的字符 T 读进窗口,它的计数加一。现在窗口里 T 有 4 个,F 有 1 个,少数派是 1 个。少数派 1 ≤ k = 1,窗口还合法,先放着,等下结算。
- 23结算这一步。窗口 [3..7] 现在合法,大小是 5,里面翻掉少数派的 1 个字符就能全部相同。它没有超过历史最长 5,ans 保持不动,窗口继续往右滑。
- 24整串滑完了。见过的最长合法窗口是 [0..4],也就是 TTFTT 这一段。它里面只有 1 个 F,把这个 F 翻成 T,整段就变成 5 个连续的 T,正好用掉 1 次操作。所以答案就是 5,跟开头看出来的对上了。
⚠️ 容易写错的地方
✗ 错:把要翻转的次数当成 max(cntT, cntF)
✓ 对:翻的是少数派,次数 = min(cntT, cntF)
要让整段相同,把个数少的那一派翻成多数派最省,翻多数派反而更亏
✗ 错:窗口不合法时,一次只缩一格就急着结算
✓ 对:用 while 一直缩到 min(cntT,cntF) ≤ k 为止
一次踢掉的字符可能还不够,少数派仍超 k,必须缩到重新合法才算这一步
✗ 错:窗口缩小后担心把已记录的最长答案弄丢
✓ 对:ans 用 max 记录历史峰值,窗口变小不影响它
ans 存的是曾经见过的最长合法窗口,后面窗口即使变短,之前的最大值依然留在 ans 里
✗ 错:以为必须分别试「最终全 T」和「最终全 F」两种再比较
✓ 对:单窗口只按少数派判合法,已同时覆盖两种情形
窗口合法就意味着翻掉少数派能让整段统一,不论最后统一成 T 还是 F,都被这一个条件包住了
完整代码(Python / C++ / Java)
Python
from __future__ import annotations
from typing import *
from collections import *
from functools import *
from itertools import *
from math import *
from heapq import *
from bisect import *
class Solution:
def maxConsecutiveAnswers(self, answerKey: str, k: int) -> int:
def f(c: str) -> int:
cnt = l = 0
for ch in answerKey:
cnt += ch == c
if cnt > k:
cnt -= answerKey[l] == c
l += 1
return len(answerKey) - l
return max(f("T"), f("F"))C++
#include <algorithm>
#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <deque>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace std;
class Solution {
public:
int maxConsecutiveAnswers(string answerKey, int k) {
int n = answerKey.size();
auto f = [&](char c) {
int l = 0, cnt = 0;
for (char& ch : answerKey) {
cnt += ch == c;
if (cnt > k) {
cnt -= answerKey[l++] == c;
}
}
return n - l;
};
return max(f('T'), f('F'));
}
};Java
import java.util.*;
class Solution {
private String s;
private int k;
public int maxConsecutiveAnswers(String answerKey, int k) {
s = answerKey;
this.k = k;
return Math.max(f('T'), f('F'));
}
private int f(char c) {
int l = 0, cnt = 0, n = s.length();
for (int r = 0; r < n; r++) {
cnt += s.charAt(r) == c ? 1 : 0;
if (cnt > k) {
cnt -= s.charAt(l++) == c ? 1 : 0;
}
}
return n - l;
}
}复杂度
时间
O(n)
n 是字符串长度。单窗口法里每个下标最多被右指针读入一次、被左指针踢出一次,合计每个字符常数次操作;参考代码的两趟法各扫一遍,仍是线性
空间
O(1)
只用到 cntT、cntF、l、ans 这几个计数变量,不随字符串变长而增加。参考代码三种语言都直接在原字符串上按下标读字符(Java 用 charAt),不额外开数组,空间同为常数
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 考试的最大困扰度 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
这题的核心结构是什么?+
一个变长滑动窗口,合法条件是窗口内少数派字符的个数不超过 k。右指针不断吃进新字符,少数派一旦超过 k 就右移左指针收缩,直到重新合法,全程用 max 记录最长合法窗口的长度。识别出「变长窗口 + 少数派个数受限」这个母题,一类题都能套。
它和最大连续 1 的个数 III 是一回事吗?+
本质一样。那道题是 0 和 1 组成的数组,最多把 k 个 0 翻成 1,求最长的连续 1;这里是 T 和 F,最多翻 k 个字符,求最长同字符段。把「翻少数派」对应到「翻 0」,就是同一个变长窗口套路,只是判定从「0 的个数 ≤ k」变成「少数派个数 ≤ k」。
参考代码为什么写成两趟?+
它把问题拆成两个子问题:一趟假设最终统一成 F,也就是把不超过 k 个 T 翻掉,求最长窗口;另一趟假设统一成 T。每趟只盯一种字符的计数,写起来更直白,最后取两趟的较大值。和单窗口双计数法结论一致,复杂度都是 O(n)。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 考试的最大困扰度 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。