
求解思路
这道题直接求解很困难,因为我们既要保证每种字符出现次数≥k,又要让子串尽可能长,这两个条件相互制约难以平衡。
但如果我们换个角度,先固定子串中必须恰好包含require种不同的字符,然后在这个约束下用滑动窗口找最长子串,问题就变得简单了。我们从require=1开始枚举到require=26(英文字母最多26种),对于每个require值,用双指针维护一个窗口,右指针不断扩展收集字符,当窗口中的字符种类数超过require时,左指针就收缩窗口把多余的字符吐出去,在满足"种类数等于require且每种字符次数都≥k"这个条件时更新答案。
代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
public static int longestSubstring(String str, int k) {
char[] s = str.toCharArray();
int n = s.length;
int[] cnts = new int[256];
int ans = 0;
// 枚举子串中字符的种类数
for (int require = 1; require <= 26; require++) {
Arrays.fill(cnts, 0);
// collect: 窗口中字符的种类数
// satisfy: 窗口中出现次数≥k的字符种类数
for (int l = 0, r = 0, collect = 0, satisfy = 0; r < n; r++) {
// 右指针扩展窗口
cnts[s[r]]++;
if (cnts[s[r]] == 1) {
collect++; // 新增一种字符
}
if (cnts[s[r]] == k) {
satisfy++; // 该字符达标
}
// 左指针收缩窗口,保证种类数不超过require
while (collect > require) {
if (cnts[s[l]] == 1) {
collect--;
}
if (cnts[s[l]] == k) {
satisfy--;
}
cnts[s[l++]]--;
}
// 当所有字符都达标时更新答案
if (satisfy == require) {
ans = Math.max(ans, r - l + 1);
}
}
}
return ans;
}
|
如果觉得有帮助,欢迎点赞、关注、转发~