第一种为暴力解法,只能得部分分,剩下的超时
#include <bits/stdc++.h>
using namespace std;
int count(string ss) {
set<char> sset;
int cnt = 0;
for (int i = 0; i < ss.size(); i++) {
if (sset.count(ss[i]) == 0) {
sset.insert(ss[i]);
cnt++;
}
}
return cnt;
}
int main() {
// string s1 = "aba";
// string s2 = "abc";
// string s3 = "aaa";
// cout << count(s1) << endl;
// cout << count(s2) << endl;
// cout << count(s3) << endl;
string S, sub;
cin >> S;
long long ans = 0;
int len = S.size();
for (int i = 0; i < len; i++) {
for (int j = i; j < len; j++) {
// strncpy(sub, S + i, (j-i+1));
sub = S.substr(i, (j-i+1)); // 从位置pos起,取出n个字符
// cout << sub << endl;
ans += count(sub);
}
}
cout << ans;
return 0;
}
正确解法如下,不同于暴力解法的计数对象是所有的子串,下面的解法对象为某个字符,对于某一个字符而言,统计满足以下条件的字符串总数:该字符在子串中第一次出现。例如,ababc,在对第二个a(已标红)进行求解时,以该a第一次出现的所有子串为:
- a, ab, abc
- ba, bab, babc
这个时候就不能将第一个a作为首字母计数了。我首先想到的是用一个while循环来找到该字符的前一个相同字符,但是时间复杂度依旧为O(n^2),可以设置一个last数组,表示字符i上次出现在原字符串中的位置,这样可以在线性时间内完成对某个字符的求解,整体的复杂度为O(n)。有一点需要注意:通过字符本身直接求得其上次出现位置,是由于字符在计算机中是以ASCII码的形式存储的。
#include <bits/stdc++.h>
using namespace std;
string ss;
int last[200]; // ascii码字符表一共128个,200个够用了
// 表示字符i上次出现的位置(从1开始编号)
long long ans = 0;
int main() {
cin >> ss;
int n = ss.size();
for (int i = 1; i <= n; i++) {
ans += (long long) (i - last[ss[i-1]]) * (n - i + 1); // 只记数 字符第一次出现的情况,该字符被认为发挥了作用
// 这里必须有一个类型转换,否则可能会爆long long 结果溢出
last[ss[i-1]] = i;
}
cout << ans;
return 0;
}