开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情
题目描述
对于一个字符串 S,我们定义 S 的分值 f(S) 为 S 中恰好出现一次的字符个数。例如 f(aba) = 1,f(abc) = 3, f(aaa) = 0。
现在给定一个字符串 S0⋯n−1(长度为 n1≤n≤10^5),请你计算对于所有 S 的非空子串Si⋯j(0≤i≤j<n),f(Si⋯j)的和是多少。
输入描述
输入一行包含一个由小写字母组成的字符串 S。
输出描述
输出一个整数表示答案。
输入输出样例
示例
输入
ababc
输出
21
解题思路
我们先来看示例中的21是怎么算出来的。
以第一个字符开头的字串有a,ab,aba,abab,ababc加起来f(s)=1+2+1+0+1=5
以第二个字符开头的字串有b,ba,bab,babc,f(s)=1+2+1+2=6
以第三个字符开头的字串有a,ab,abc,f(s)=1+2+3=6
以第四个字符开头的字串有b,bc,f(s)=1+2=3
以第五个字符开头的字串有c,f(s)=1
总的=5+6+6+3+1=21
本题如果用这种模拟的方式去找字符串的子串再去求每个字串的f(s),当n很大的时候会执行非常长的时间。
我们换一种思路,去看每一个字符的贡献度
第一个字符a在哪些字串贡献了 a,ab, 2
第二个字符b, aba,ab,b,ba,4
第三个字符a, ba,bab,babc,a,ab,abc, 6
第四个字符b, ab,abc,b,bc,4
第五个字符c, ababc,babc,abc,bc,c 5
从中我们可以发现字符的贡献度在于其左右两边出现的位置,等于当前位置减去左边的位置*右边出现的位置-当前的位置。以示例中第三个字符a为例
找到贡献度的方法后,就能求出全部字符的贡献度
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
String s = scan.nextLine();
int n = s.length();
// 找到每一个字符前面一个和后面一个相同字符的下标
int[][] arr = new int[2][n];
// 默认左边下标为-1,右边为n
Arrays.fill(arr[0],-1);
Arrays.fill(arr[1],n);
// 记录26个字符最新的位置
int[] location = new int[26];
Arrays.fill(location,-1);
// 找左右字符位置
for (int i = 0; i < n; i++) {
char c = s.charAt(i);
if (location[c - 'a'] != -1) {
arr[1][location[c - 'a']] = i; // 设置上一个数的右边值
}
arr[0][i] = location[c - 'a']; // 设置当前数的左边值
location[c - 'a'] = i;
}
int ans = 0;
// (当前位置 - 左边位置) * (右边位置 - 当前位置)
for (int i = 0; i < n; i++) {
ans += (i - arr[0][i]) * (arr[1][i] - i);
}
System.out.println(ans);
scan.close();
}
}