题目描述
给你一个字符串 s
和一个整数 k
,在 s
的所有子字符串中,请你统计并返回 至少有一个 字符 至少出现 k
次的子字符串总数。
子字符串 是字符串中的一个连续、 非空 的字符序列。
样例
输入: s = "abacb", k = 2
输出: 4
解释:
符合条件的子字符串如下:
"aba"(字符 'a' 出现 2 次)。
"abac"(字符 'a' 出现 2 次)。
"abacb"(字符 'a' 出现 2 次)。
"bacb"(字符 'b' 出现 2 次)。
输入: s = "abcde", k = 1
输出: 15
解释:
所有子字符串都有效,因为每个字符至少出现一次。
限制
1 <= s.length <= 3000
1 <= k <= s.length
s
仅由小写英文字母组成。
算法
(双指针) O(n+|Σ|)
- 对于每一个位置 i,找到尽可能小的位置 j,满足区间 [j,i] 构成的子串 不 满足条件。
- 注意到,随着 i 的移动,j 是单调不减的,所以考虑使用双指针。
- 在遍历过程中,维护当前区间 [j,i] 中每个字母的出现次数,以及有多少个字母满足至少 k 个。
- 如果发现区间满足条件了,则不断移动 j 直到不满足条件。
- 以 i 为结尾满足条件的区间有 [0,i],[1,i],…,[j−1,i] 共 j 个。
时间复杂度
- 预处理数组的时间复杂度为 O(|Σ|)。
- 每个位置仅被遍历两次,每次遍历的操作复杂度为常数。
- 故总时间复杂度为 O(n+|Σ|)。
空间复杂度
- 需要 O(|Σ|) 的额外空间存储当前区间每个字母的出现次数。
C++ 代码
class Solution {
public:
int numberOfSubstrings(string s, int k) {
const int n = s.size();
int ans = 0;
vector<int> cnt(26, 0);
int m = 0;
for (int i = 0, j = 0; i < n; i++) {
++cnt[s[i] - 'a'];
if (cnt[s[i] - 'a'] == k)
++m;
while (m > 0) {
--cnt[s[j] - 'a'];
if (cnt[s[j] - 'a'] == k - 1)
--m;
++j;
}
ans += j;
}
return ans;
}
};