题目描述
难度分:1300
输入长度≤106的字符串s,只包含小写英文字母。
输出有多少个子串,以heavy
开头,并以metal
结尾。
输入样例1
heavymetalisheavymetal
输出样例1
3
输入样例2
heavymetalismetal
输出样例2
2
输入样例3
trueheavymetalissotruewellitisalsosoheavythatyoucanalmostfeeltheweightofmetalonyou
输出样例3
3
算法
枚举+二分查找
能写二分我就不会写双指针,我们可以先遍历s串,分别将heavy
和metal
的出现位置存入pos1和pos2两个位置数组中,本题给定的前后缀比较短,可以直接暴力,如果很长的话就需要用字符串哈希或者kmp。
然后遍历pos1的每个位置x,以x开头的子串如果要以某个metal
结尾,那这个metal
的出现位置就至少要是x+5,因此可以二分查找pos2数组,找到第一个符合条件的位置j。j开始一直到pos2的最后一个位置都可以和x组成heavy
开头metal
结尾的子串,pos2.size()−j就是x能够贡献的子串数目。
复杂度分析
时间复杂度
预处理出pos1和pos2两个位置数组,时间复杂度为O(n)。接下来遍历pos1中的每个位置x,找到符合题意的pos2中的位置,每个x要对pos2进行一次二分查找,而pos1和pos2的规模在最差情况下都是O(n)的,所以二分查找的时间复杂度为O(log2n)。因此,整个算法的时间复杂度为O(nlog2n)。
空间复杂度
两个位置数组pos1和pos2,都是线性空间,额外空间复杂度为O(n)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1000010;
char s[N];
int n;
int main() {
scanf("%s", s + 1);
n = strlen(s + 1);
vector<int> pos1, pos2;
for(int i = 1; i + 4 <= n; i++) {
if(s[i] == 'h' && s[i + 1] == 'e' && s[i + 2] == 'a' && s[i + 3] == 'v' && s[i + 4] == 'y') {
pos1.push_back(i);
}
if(s[i] == 'm' && s[i + 1] == 'e' && s[i + 2] == 't' && s[i + 3] == 'a' && s[i + 4] == 'l') {
pos2.push_back(i);
}
}
long long ans = 0;
for(int x: pos1) {
ans += pos2.end() - lower_bound(pos2.begin(), pos2.end(), x + 5);
}
printf("%lld\n", ans);
return 0;
}