2516. 每种字符至少取 K 个
给你一个由字符 'a'
、'b'
、'c'
组成的字符串 s
和一个非负整数 k
。每分钟,你可以选择取走 s
最左侧 还是 最右侧 的那个字符。
你必须取走每种字符 至少 k
个,返回需要的 最少 分钟数;如果无法取到,则返回 -1
。
示例 1:
**输入:**s = “aabaaaacaabc”, k = 2
**输出:**8
解释:
从 s 的左侧取三个字符,现在共取到两个字符 ‘a’ 、一个字符 ‘b’ 。
从 s 的右侧取五个字符,现在共取到四个字符 ‘a’ 、两个字符 ‘b’ 和两个字符 ‘c’ 。
共需要 3 + 5 = 8 分钟。
可以证明需要的最少分钟数是 8 。
示例 2:
**输入:**s = “a”, k = 1
输出:-1
**解释:**无法取到一个字符 ‘b’ 或者 ‘c’,所以返回 -1 。
提示:
1 <= s.length <= 105
s
仅由字母'a'
、'b'
、'c'
组成0 <= k <= s.length
题解
这里可以想到滑动窗口的变种。我左边一个指针,右边一个指针。左边的先顶到满足的最右边,然后右边顶向左边的同时,左边的指针可以弹出来
这个过程中不断刷新最小值
public static int takeCharacters(String s, int k) {
// 变相滑动窗口
int[] count = new int[3];
char[] charArray = s.toCharArray();
int index = 0;
int length = charArray.length;
for (int i = 0; i < length; i++) {
if (!verify(count, k)) {
if (charArray[i] == 'a') {
count[0]++;
} else if (charArray[i] == 'b') {
count[1]++;
} else if (charArray[i] == 'c') {
count[2]++;
}
index++;
} else {
break;
}
}
if (!verify(count, k)) {
return -1;
}
int res = index;// 个数
int rightIndex = 0;
for (int i = length - 1; i > 0 && index > 0 && rightIndex < res; i--) {
rightIndex++;
if (charArray[i] == 'a') {
count[0]++;
} else if (charArray[i] == 'b') {
count[1]++;
} else if (charArray[i] == 'c') {
count[2]++;
}
// 先加上最后面一位,然后弹出index的位数
while (verify(count, k) && index > 0) {
index--;
if (charArray[index] == 'a') {
count[0]--;
} else if (charArray[index] == 'b') {
count[1]--;
} else if (charArray[index] == 'c') {
count[2]--;
}
}
// 如果还是满足,就不需要加一
if (verify(count, k)) {
res = Math.min(res, index + rightIndex);
} else {
res = Math.min(res, index + rightIndex + 1);
}
}
return res;
}
public static boolean verify(int[] count, int k) {
return count[0] >= k && count[1] >= k && count[2] >= k;
}
这里index就是左边的个数,leftIndex就是右边的个数。单独命名代码好写点。
退出语句皆是下 i > 0 && index > 0 && rightIndex < res
i一定要大于0不说了,index如果是0,说明左指针已经弹完了,就可以退出了,还有如果右指针弹入的数量比刷新的值还大,也没有继续的意义了
这里的if是可以优化的
public static int takeCharacters2(String s, int k) {
// 变相滑动窗口
int[] count = new int[3];
char[] charArray = s.toCharArray();
int index = 0;
int length = charArray.length;
for (int i = 0; i < length; i++) {
if (!verify(count, k)) {
count[charArray[i] - 'a']++;
index++;
} else {
//index--;// 到了4更新 然后来到5发现成功了,其实index在4的时候已经成功了 这里减去1
break;
}
}
if (!verify(count, k)) {
return -1;
}
int res = index;// 个数
int rightIndex = 0;
for (int i = length - 1; i > 0 && index > 0 && rightIndex < res; i--) {
rightIndex++;
count[charArray[i] - 'a']++;
// 先加上最后面一位,然后弹出index的位数
while (verify(count, k) && index > 0) {
index--;
count[charArray[index] - 'a']--;
}
// 如果还是满足,就不需要加一
if (verify(count, k)) {
res = Math.min(res, index + rightIndex);
} else {
res = Math.min(res, index + rightIndex + 1);
}
}
return res;
}
public static boolean verify(int[] count, int k) {
return count[0] >= k && count[1] >= k && count[2] >= k;
}
总结
其实滑动窗口的代码并不好写,涉及到临界的判断。如果是第一次写,很可能会自闭。我已经写过很多次了,还是不能一笔写完
这里花了这么多时间是想着把index和rightIndex融入到for循环。
然后这个忘记写了
// 如果还是满足,就不需要加一
if (verify(count, k)) {
res = Math.min(res, index + rightIndex);
} else {
res = Math.min(res, index + rightIndex + 1);
}
这不是最优解,但是最近比较忙,就不研究最优解了