1915-最美子字符串的数目-另类前缀和

写在前面

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;
    }
}

代码很短,也不难理解,最核心的思路就是通过前缀和得到区间中最美子字符串的个数,这里还是需要反复揣摩。
如果文章有任何问题,还请指出。
感恩相遇~~~

你可能感兴趣的:(1915-最美子字符串的数目-另类前缀和)