1358. 包含所有三种字符的子字符串数目
题目
给你一个字符串 s
,它只包含三种字符 a, b 和 c 。
请你返回 a,b 和 c 都 至少 出现过一次的子字符串数目。
示例 1:
输入:s = "abcabc"
输出:10
解释:包含 a,b 和 c 各至少一次的子字符串为 "abc", "abca", "abcab", "abcabc", "bca", "bcab", "bcabc", "cab", "cabc" 和 "abc" (相同字符串算多次)。
示例 2:
输入:s = "aaacb"
输出:3
解释:包含 a,b 和 c 各至少一次的子字符串为 "aaacb", "aacb" 和 "acb" 。
示例 3:
输入:s = "abc"
输出:1
提示:
3 <= s.length <= 5 x 10^4
s
只包含字符 a,b 和 c 。
思路
其实滑窗的思想来源于暴力枚举,只不过把之前O(n*n)中的一个n变为了O(1),减少了重复计算,一个右端点的计算里面不用继续枚举左端点了
大概的思路和之前一样枚举右端点,用哈希表记录字符出现次数,符合条件时收缩并更新答案。
这里我们分析下什么时候更新答案?
- 枚举右端点开始,左端点不动,此时不满足条件,不更新
- 满足条件,左端点开始收缩,每收缩一次,我们上一次都是合法的,要记录下来
- 左端点收缩过一次之后,右端点继续向后,此时,虽然窗口不满足条件,但是如果带上字符串左边的部分,就能够满足了,
重点就是这个第三点,我来举个例子:s="abcaaabc"时,
当right遍历到4,left由于之前收缩,现在在2.
此时,虽然窗口内caa不满足,但是以此窗口为右侧部分,左端点为[0,left-1]的字符串都是满足的,刚好是 left 个字符串
class Solution {
public int numberOfSubstrings(String s) {
char[] sc = s.toCharArray();
HashMap<Character, Integer> dis = new HashMap<>();
int count = 0, left = 0;
for (int right = 0; right < s.length(); right++) {
dis.merge(sc[right], 1, Integer::sum);
while (appearOnce(dis)) {
dis.merge(sc[left], -1, Integer::sum);
left++;
}
// 收缩后,有left个子区间满足,
// 当窗口内不满足条件时,所有以右端点为结尾,
// 以[0,left-1]为左端点的字符串都是合法的
count += left;
}
return count;
}
private boolean appearOnce(HashMap<Character, Integer> dis) {
return dis.getOrDefault('a', 0) > 0 &&
dis.getOrDefault('b', 0) > 0 &&
dis.getOrDefault('c', 0) > 0;
}
}