1371. 每个元音包含偶数次的最长子字符串【前缀和 + 状态压缩】

原题地址:LeetCode 1371. 每个元音包含偶数次的最长子字符串

题目:

示例 1:

输入:s = “eleetminicoworoep”
输出:13
解释:最长子字符串是 “leetminicowor” ,它包含 e,i,o 各 2 个,以及 0 个 a,u 。

示例 2:

输入:s = “leetcodeisgreat”
输出:5
解释:最长子字符串是 “leetc” ,其中包含 2 个 e 。

示例 3:

输入:s = “bcbcbc”
输出:6
解释:这个示例中,字符串 “bcbcbc” 本身就是最长的,因为所有的元音 a,e,i,o,u 都出现了 0 次。

提示:

1 <= s.length <= 5 x 10^5
s 只包含小写英文字母。

思路

前缀和:

1.对于长度为 L 的字符串 S, 用一个辅助数组int flag[L][5]来记录 ,flag[x][0…5] 表示从 S[0] 到 S[x] 这个子串中 aeiou的个数 。这步需要用 O(n)时间 。
2.此时 则有 子串 S[x1] 至 S[x2] 中 aeiou 的个数 是 (flag[x2][0…5] 减 flag[x1][0…5] )
3.每个子串的aeiou的个数 已知,则需要 O(n^2) 时间遍历所有子串 , O(n^2)太大,还需要继续优化。

状态压缩:


4.根据题意,只需要判断次数是不是偶数,而不关心具体出现多少次 。所以 ,每个字母的状态用0,1两个状态就可以表示奇偶。



5.进一步可以推出: 如果 子串 S[x1] 至 S[x2] 包含字母 aeiou 的次数是偶数,则 flag[x1][0…5] 与 flag[x2][0…5] 相等 。由于1个字母的状态只需要1位二进制表示,5个字母只需要5位便可表示所有状态。
所以辅助数组可以缩减为char flag[L]。


6.还可以再优化,5个字母一共2^5 = 32中状态 ,我们可以把定义一个 大小为32的数组来记录 每种状态第一次出现的下标 ,例如: 假设 遍历到 S[x] 时 第一次出现二进制状态 (11111)(即所有字母都出现奇数次) 。 则 记 flag[31] = x (二进制11111 转10进制为 31) ,当 遍历到 S[x2]时 遇到同样状态,则此时 S的子串 S[x] 至S[x1] 中各字母的个数都为偶数
7.所以只需要遍历一遍字符串即可得出结果 时间复杂度 O(n) ,空间复杂度为常数 O(1)


代码:

    int findTheLongestSubstring(string s) {
        int flag[32] ;
        for(int i = 0;i < 32; i++){
            flag[i] = -2;
        }
        flag[0] = -1;
        int curState = 0;
        int maxLen = 0;
        for(int i = 0 ; i < s.length() ; i ++){
            switch (s[i]){
                case 'a': curState ^= (1 << 0); break;
                case 'e': curState ^= (1 << 1); break;
                case 'i': curState ^= (1 << 2); break;
                case 'o': curState ^= (1 << 3); break;
                case 'u': curState ^= (1 << 4); break;
                default: break;
            }
            if(flag[curState] != -2){
                maxLen = maxLen > (i - flag[curState] ) ? maxLen : (i - flag[curState] );
            }
            else{
                flag[curState] = i;
            }
        }
        return maxLen;
    }

你可能感兴趣的:(leetcode)