写在前面
247场周赛第三题,没想到使用前缀和,看到大佬们十几行就做完了真的佩服。本文主要讲解思路,并配以完整代码供参考。
题目
最近力扣题目翻译的真是越来越晦涩了,比赛的时候看了半天才看懂啥事最美字符串。通俗来说,统计某字符串中各种字符出现的次数,其中,出现次数为奇数次的字符不超过一个,则称该字符串为最美字符串。
核心思路
最简单直接的思路:枚举每个子串,并判断是否为最美字符串,统计答案。
判断是否为最美字符串
一种朴素的思想,使用一个cnts数组
,存储每种字符出现的次数,然后遍历cnts数组
判断是否奇数不超过一个即可。不过,题目中给出字符串只由前十个小写英文字母组成,并且只关注字符出现的是奇数次还是偶数次(只由两种选择),所以使用一个10bit的二进制数
来记录一个字符串的状态还是很容易想到的,只要使用bitCount
得到结果小于等于1
,则该字符串为最美字符串。
问题
如果通过枚举+判断的方式统计结果,单纯枚举所有子串的时间复杂度为O(N ^ 2),已经无法满足10 ^ 5
的数据量,更不用说遍历字符串得到是否为最美字符串了。
解决方案
既然枚举子串不能解决问题,肯定要将某个区间中的结果一次性计算出来,而常见的区间处理的方式就是前缀和了。我们不妨使用sum[i]
表示前i
个字符组成的子串的状态(即上述对应的二进制数),那么什么样的sum[i]和sum[j](j < i)
是有关系的呢?我们通过下图理解。
可以看到状态值相同的两个状态中间可以形成一个答案,状态值有一位不同的两个状态之间也可以形成一个答案。而题目中只需要求出最美子字符串的个数即可,那么,我们不妨使用一个数组存储遍历到位置
i
时,状态sum
出现次数,就可以得到如下的状态转移:
res += cnts[sum]
for(int i = 0; i < 10; i++){
res += cnts[sum ^ (1 << i)];
}
这样我们就可以得到完整代码了。
完整代码
class Solution {
public long wonderfulSubstrings(String word) {
int sum = 0;
int[] cnts = new int[1 << 10];
cnts[0] = 1;
long res = 0;
for(char c : word.toCharArray()){
sum = sum ^ (1 << (c - 'a'));
res += cnts[sum];
for(int i = 0; i < 10; i++){
res += cnts[sum ^ (1 << i)];
}
cnts[sum]++;
}
return res;
}
}
代码很短,也不难理解,最核心的思路就是通过前缀和得到区间中最美子字符串的个数,这里还是需要反复揣摩。
如果文章有任何问题,还请指出。
感恩相遇~~~