题目地址:
https://leetcode.com/problems/longest-substring-with-at-least-k-repeating-characters/
给定一个长 n n n的字符串 s s s,题目保证 s s s里只有英文小写字母,再给定一个数 k k k,求其每个字母出现次数都不低于 k k k次的最长子串的长度。
法1:分治法。先求一下每个字母在 s s s的长 i i i前缀里出现的次数,记为数组 p p p,则 p [ a ] [ i ] p[a][i] p[a][i]指的是 s [ 0 : i − 1 ] s[0:i-1] s[0:i−1]中字母 a a a出现的次数。接下来可以用分治法了。考虑区间 [ l , r ] [l,r] [l,r],遍历该区间,如果发现某个字母 s [ j ] s[j] s[j]在该区间里出现次数没有达到 k k k,那么该字母一定不在合法解里,则可以递归求解 [ l , j − 1 ] [l,j-1] [l,j−1]和 [ j + 1 , r ] [j+1,r] [j+1,r]这两个区间里的合法解,两个解大的就是 [ l , r ] [l,r] [l,r]的全局解;当然遇到 l > r l>r l>r的时候就直接返回 0 0 0了;如果遍历完区间并没有发现出现次数没达到 k k k的字母,那说明整个区间就是合法解,则返回 r − l + 1 r-l+1 r−l+1。代码如下:
public class Solution {
public int longestSubstring(String s, int k) {
// 求一下出现次数的前缀和数组
int[][] preCount = new int[26][s.length() + 1];
for (int i = 0; i < s.length(); i++) {
for (char ch = 'a'; ch <= 'z'; ch++) {
preCount[ch - 'a'][i + 1] = preCount[ch - 'a'][i];
}
preCount[s.charAt(i) - 'a'][i + 1] = preCount[s.charAt(i) - 'a'][i] + 1;
}
return dfs(s, 0, s.length() - 1, preCount, k);
}
private int dfs(String s, int l, int r, int[][] preCount, int k) {
// 如果区间空了,则返回0
if (l > r) {
return 0;
}
for (int i = l; i <= r; i++) {
if (preCount[s.charAt(i) - 'a'][r + 1] - preCount[s.charAt(i) - 'a'][l] < k) {
// 这里可以优化一下,i是最左边的次数小于k的字符位置,可以继续向右略过次数小于k的字符
int right = i;
while (right <= r && preCount[s.charAt(right) - 'a'][r + 1] - preCount[s.charAt(right) - 'a'][l] < k) {
right++;
}
return Math.max(dfs(s, l, i - 1, preCount, k), dfs(s, right, r, preCount, k));
}
}
// 走到这一步了,说明整个区间都是合法解,返回区间长度
return r - l + 1;
}
}
时间复杂度 O ( n 2 ) O(n^2) O(n2),空间 O ( n ) O(n) O(n)。
法2:双指针。为了增加单调性质,我们枚举区间的不同字母个数,这个个数的范围显然是 1 ∼ 26 1\sim 26 1∼26。例如在枚举字母个数是 c c c的时候,当区间不同字母个数等于 c c c的时候,我们看一下不同字母个数是否等于出现次数不少于 k k k的字母个数,如果相等,则能更新答案;如果一旦区间不同字母个数超过 k k k了,就可以右移左端点了。这样就有了单调性。代码如下:
import java.util.Arrays;
public class Solution {
public int longestSubstring(String s, int k) {
int res = 0, diffCount = 0, kCount = 0;
int[] count = new int[26];
// 枚举区间不同字母个数
for (int c = 1; c <= 26; c++) {
// 每次循环开始的时候要重置一下各个变量
Arrays.fill(count, 0);
diffCount = kCount = 0;
for (int i = 0, j = 0; i < s.length(); i++) {
char chi = s.charAt(i);
count[chi - 'a']++;
if (count[chi - 'a'] == 1) {
diffCount++;
}
if (count[chi - 'a'] == k) {
kCount++;
}
// 如果不同字母个数大于c了,则要右移左端点
if (diffCount > c) {
while (diffCount > c) {
char chj = s.charAt(j);
count[chj - 'a']--;
if (count[chj - 'a'] == 0) {
diffCount--;
}
if (count[chj - 'a'] == k - 1) {
kCount--;
}
j++;
}
} else if (diffCount == c) {
if (diffCount == kCount) {
res = Math.max(res, i - j + 1);
}
}
}
}
return res;
}
}
时间复杂度 O ( n ) O(n) O(n),空间 O ( 1 ) O(1) O(1)。