题目序号:41
题目难度:中等
题目背景/描述:
小F得到了一个特殊的字符串,这个字符串只包含字符A、S、D、F,其长度总是4的倍数。他的任务是通过尽可能少的替换,使得A、S、D、F这四个字符在字符串中出现的频次相等。求出实现这一条件的最小子串长度。
测试用例示例如图:
1.题目理解:首先根据题目描述,我们不难理解这道题目实际上是字符串相关算法的最短编辑距离算法Edit Distance。题目说明字符串长度总是4,并且只包括,ASDF四种字符,我们需要的是调整四种字符的出现次数使其频率相同。即只允许使用替换的最短编辑距离算法,当然本题使用了滑动窗口来进行查找,而不是最短编辑距离算法传统的动态规划,也算是一个比较巧妙的点了。
2.题目思路: 由于题目说只包含着四种字符,又要求其频次相同,所以每个字符的频次一定是总长度的四分之一。
计算频次:我们首先需要知道当前每种字符都有多少个,然后除以总长度即可获取到每种字符当前的频次 确定最终符合要求的频次:根据我们前面说的,最终应该让每一种字符的频次都变为字符串总长的四分之一。
滑动窗口:最终使用滑动窗口技术来找到符合题目要求的最小的子串,通过替换该子串中的字符就可以达到目标频次。
这里滑动窗口用了比较巧妙的思路,从左向右查找子串,遇到某种字符就使对应字符的数量减1,如果某一时刻四种字符的数量都小于总长度的四分之一,说明此时通过替换已经可以满足题目要求了。
3.代码
public static int solution(String input) {
// Please write your code here
int[] cnt = new int[26];
for (int i = 0; i < input.length(); i++) {
char c = input.charAt(i);
cnt[idx(c)]++;
}
int partial = input.length() / 4;
int res = input.length();
if (check(cnt, partial)) {
return 0;
}
for (int l = 0, r = 0; l < input.length(); l++) {
while (r < input.length() && !check(cnt, partial)) {
cnt[idx(input.charAt(r))]--;
r++;
}
if (!check(cnt, partial)) {
break;
}
res = Math.min(res, r - l);
cnt[idx(input.charAt(l))]++;
}
return res;
}
public static int idx(char c) {
return c - 'A';
}
public static boolean check(int[] cnt, int partial) {
if (cnt[idx('A')] > partial || cnt[idx('S')] > partial || cnt[idx('D')] > partial || cnt[idx('F')] > partial) {
return false;
}
return true;
}
public static void main(String[] args) {
// You can add more test cases here
System.out.println(solution("ADDF") == 1);
System.out.println(solution("ASAFASAFADDD") == 3);
}
}
4.代码讲解: 首先我们计算出目标频次,然后用check函数去检验当前的字符串是否符合要求。如果满足直接返回0。check函数会检查四种字符是否都小于总长度的四分之一,如果都小于那么说明满足要求,反之不满足题目要求。
由于子串的开头我们并不能确定,所以我们使用滑动窗口,从左往右以l为下标,逐个查询经过调整就能满足要求的最短子串,使用一个while循环,然后以r为下标从左向右,遇到某类字符就将某类字符的数量减去1,如果发现不平衡,那么就继续向右,这样就能够找到以l为左侧的经过调整能平衡的最短子串。之后不断向右移动l,当然此时不必调整r,因为如果l之前的子串能够到r,那么说明l之后的子串也都能到r
最后比较一下每个l的最短子串长度,选取最小值作为最后的res即可
这里还需要说明的是我们使用了一个26长度的数组来进行“桶排序”,idx函数只是为了求得四种字符距离“A”字符的ASCII距离,用于放在cnt数组之中。不这样做也可以,之所以我选择这么做是为了给代码一定的泛用性,如果更换为其他类型的字符,那么只需要调整check函数的判断条件即可。