至少有K个重复字符的最长子串(分治,递归,滑动窗口)
给你一个字符串 s
和一个整数 k
,请你找出 s
中的最长子串, 要求该子串中的每一字符出现次数都不少于 k
。返回这一子串的长度。
解法一、分治
-
从题目中找出华点
首先,要找的子串是一个连续的子串
其次,该子串中的每一字符出现次数都不少于
k
那么,串中的所有不满足次数的字符一定不在所求区间内,
所以,区间就可以被这样的字符一个个分开。
最后,问题区间,被一次次分解,这,就是分治递归.
class Solution {
private int[] cnt = new int[26];
private int k ;
public int longestSubstring(String s, int k) {
this.k = k;
if(k==1) return s.length();
return digui(s,0,s.length()-1);
}
public int digui(String s, int i, int j)
{
if(i>j||j-i+1<k) return 0;
int longK = 0,left = i,right = 0;
Queue<Integer> queue =new LinkedList<>();
Arrays.fill(cnt, 0);
//统计区间内每个字符出现的次数
for(int z = i; z <= j; ++z){
cnt[s.charAt(z)-'a']++;
}
//队列标记区间内字符不满足条件的索引
boolean isOk = true;
for(int z = i; z <= j; ++z){
if(cnt[s.charAt(z)-'a']<k){
isOk =false; //标记整个区间不是一个满足条件的区间
queue.offer(z);
}
}
// 字符再分成一个又一个区间递归下去
while(!queue.isEmpty()){
right = queue.poll()-1;
//System.out.println(left+":"+right);
longK = Math.max(longK, digui(s,left,right));
left = right+2;
}
//特殊处理最后一个不满足条件字符的右区间
if(left!=i)longK = Math.max(longK, digui(s,left,j));
if(isOk) return j-i+1;
else return longK;
}
}
时间复杂度: O(ns) , n是字符串长度 ,s是字符集,每次都去除一个字符,递归的深度最大为26, s=26
空间复杂度: O( s 2 s^2 s2)
解法二、滑动窗口(双指针)
-
看到这个题,首先想到的就是滑动窗口。
滑动窗口法,也叫尺取法,可以用来解决一些查找满足一定条件的连续区间的性质(长度等)的问题。由于区间连续,因此当区间发生变化时,可以通过旧有的计算结果对搜索空间进行剪枝,这样便减少了重复计算,降低了时间复杂度。往往类似于“请找到满足xx的最x的区间(子串、子数组)的xx”这类问题都可以使用该方法进行解决。
-
本题窗口右边界滑动的时候,无论窗口内区间如何,都无法判断出左边界是否应该右滑,所以就要遍历所有的子串了
-
本题的滑动窗口因此可以剪枝了,因为不确定窗口内所包含的字符数量,所以要枚举26个字符的情况。
class Solution {
private int[] cnt = new int[26];
private int k ;
public int longestSubstring(String s, int k) {
//滑动窗口枚举26个字符的情况
this.k = k;
int longK = 0;
if(k==1) return s.length();
for(int i = 1; i <= 26; i++){
longK = Math.max(longK, fixedCharInWindow(s, i));
}
return longK;
}
public int fixedCharInWindow(String s, int typeCount)
{
Arrays.fill(cnt, 0);
int p = 0; //目前窗口内字符的种类数量
int sum = 0; //窗口内达标数量
int res = 0; //结果
int left, right;
for(right = 0, left = 0; right<s.length(); ++right){
int u = s.charAt(right) - 'a';
cnt[u]++;
if(cnt[u] == 1) p++; //达标种类
if(cnt[u] == k) sum++; //达标数量
while(p>typeCount){ //字符种类数量不对,开始剪枝
u = s.charAt(left++) - 'a';
if(--cnt[u] == 0) p--;
if(cnt[u]==k-1) sum--;
}
if(sum == p ){
//每个字符数量达标,种类数量达标
res = Math.max(res, right-left+1);
}
}
return res;
}
}