395. 至少有K个重复字符的最长子串
思路:
看到这种题,第一反应是使用双指针,但是只遍历一趟显然是得不出结果的。
首先,如果使用双指针分别从头和尾部遍历,一旦头部或者尾部出现了不满足条件(出现次数小于k)的字母,两个指针就会移动。当两个指针停止移动时不代表中间的字符串都满足要求,这时开始遍历中的的字符串。
那么一旦中间又出现不满足条件(出现次数小于k)的字母,这两个指针间的字符串就不再满足条件了!所以,我们可以以这个不满足条件的字符为分界点,将这个字符串分成两段,再次验证这两段是否满足条件,也就是采用递归的方式。
于是有:
public int longestSubstring(String s, int k) {
int len = s.length();
if (len == 0 || k > len) return 0;
if (k < 2) return len;
return count(s.toCharArray(), k, 0, len - 1);
}
private int count(char[] chars, int k, int p1, int p2) {//包含p1,p2
if (p2 - p1 + 1 < k) return 0;
int[] times = new int[26]; // 26个字母
// 统计出现频次
for (int i = p1; i <= p2; ++i) {
++times[chars[i] - 'a'];
}
// 如果该字符出现频次小于k,则不可能出现在结果子串中
// 分别排除,然后挪动两个指针
while (p2 - p1 + 1 >= k && times[chars[p1] - 'a'] < k) {
++p1;
}
while (p2 - p1 + 1 >= k && times[chars[p2] - 'a'] < k) {
--p2;
}
if (p2 - p1 + 1 < k) return 0;
// 得到临时子串,再递归处理
for (int i = p1; i <= p2; ++i) {
// 如果第i个不符合要求,切分成左右两段分别递归求得
if (times[chars[i] - 'a'] < k) {
return Math.max(i - p1, count(chars, k, i + 1, p2));//错误
}
}
return p2 - p1 + 1;
}
然后就。。报错了,问题就在 return Math.max(i - p1, count(chars, k, i + 1, p2)); 这句,我以为从p1到i这段已经被验证过了所以无需再次遍历可以直接得出结果,但是!这样是存在漏洞的,比如虽然p1到i这段出现过的字符都是频率大于或者等于k的,但是未必他的所有相同字符都出现在这段被截断过的p1到i段(因为times是统计p1到p2这段得出,而不是p1到i段!),所以还需要再次验证(再次递归)。
正确代码:
public int longestSubstring(String s, int k) {
int len = s.length();
if (len == 0 || k > len) return 0;
if (k < 2) return len;
return count(s.toCharArray(), k, 0, len - 1);
}
private int count(char[] chars, int k, int p1, int p2) {//包含p1,p2
if (p2 - p1 + 1 < k) return 0;
int[] times = new int[26]; // 26个字母
// 统计出现频次
for (int i = p1; i <= p2; ++i) {
++times[chars[i] - 'a'];
}
// 如果该字符出现频次小于k,则不可能出现在结果子串中
// 分别排除,然后挪动两个指针
while (p2 - p1 + 1 >= k && times[chars[p1] - 'a'] < k) {
++p1;
}
while (p2 - p1 + 1 >= k && times[chars[p2] - 'a'] < k) {
--p2;
}
if (p2 - p1 + 1 < k) return 0;
// 得到临时子串,再递归处理
for (int i = p1; i <= p2; ++i) {
// 如果第i个不符合要求,切分成左右两段分别递归求得
if (times[chars[i] - 'a'] < k) {
return Math.max(count(chars, k, p1, i - 1), count(chars, k, i + 1, p2));
//return Math.max(i - p1, count(chars, k, i + 1, p2));
}
}
return p2 - p1 + 1;
}