1371. 每个元音包含偶数次的最长子字符串

1371. 每个元音包含偶数次的最长子字符串 - 力扣(LeetCode)

给你一个字符串 s ,请你返回满足以下条件的最长子字符串的长度:每个元音字母,即 ‘a’,‘e’,‘i’,‘o’,‘u’ ,在子字符串中都恰好出现了偶数次。

示例 1:

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

示例 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,以保存不包含的状态

题目中可以使用 pre[i][5] 来存储 5 个元音的数量
pre[i][k] - pre[j][k] 判断元音数量是否相等,并且可以统计长度

状态压缩

只使用前缀和,不可避免地依旧需要遍历 O(n^2) 复杂度

要求统计偶数,可以使用数的奇偶性来进行统计
奇数 - 奇数 = 偶数
偶数 - 偶数 = 偶数

用 0 代表偶数
用 1 代表奇数
当各个元音的奇偶性相同时,两个位置的前缀和相减后元音的偶数个数就必定是 偶数

使用二进制数表示各元音个数的奇偶性

if(s.charAt(i)=='a'){
	cur = cur ^ (1<<0);
}else if(s.charAt(i)=='e'){
	cur = cur ^ (1<<1);
}else if(s.charAt(i)=='i'){
	cur = cur ^ (1<<2);
}else if(s.charAt(i)=='o'){
	cur = cur ^ (1<<3);
}else if(s.charAt(i)=='u'){
	cur = cur ^ (1<<4);
}
1 << 0 表示二进制数 1 左移 0 位,结果就是 1 (二进制为 00001)。
1 << 1 表示二进制数 1 左移 1 位,结果就是 2 (二进制为 00010)。
1 << 2 表示二进制数 1 左移 2 位,结果就是 4 (二进制为 00100)。
1 << 3 表示二进制数 1 左移 3 位,结果就是 8 (二进制为 01000)。
1 << 4 表示二进制数 1 左移 4 位,结果就是 16 (二进制为 10000)。

压缩后可以使用 异或运算 进行判断
如果新位置的字符是元音,则对对应的位置进行异或,而不需要统计数量

pre[j] = {0 0 1 0 0}
新位置为 i
pre[j+1] = pre[j] ^ 00100 = 00000

在记录奇偶性的同时,查找之前是否出现过相同奇偶性的状态
记录最早出现该状态的位置,就可以在遍历统计时,同时完成以当前下标为后缀的最长偶数元音个数字符串长度的计算

  • 不存在当前状态,记录当前状态
  • 已存在当前状态,统计长度

使用 map 记录
key 为压缩状态表
value 为最早出现的位置

也可以使用数组记录
5 个元音,对应长度为 1111 32 长度的数组
对应的状态下标存储最早出现的位置

前缀表的下标问题

前缀表需要记录空的情况,占用了 0
1371. 每个元音包含偶数次的最长子字符串
使用了前缀表,初始为 0 的情况记录了下标 0
在后续记录时,就需要对 i + 1

可以不使用 0 下标,而是使用 -1 记录初始 0 的情况
那后续记录时就不需要 + 1
参考 LeetCode - 1371 每个元音包含偶数次的最长子字符串(Java & JS & Python & C)_leetcode 1371-CSDN博客

题解

使用长度为 2^5 长度记录所有可能的奇偶状态
0 表示 a
1 表示 e
2 表示 i
3 表示 o
4 表示 u

00001 十进制 1 表示 a 有奇数个
class Solution {
    public int findTheLongestSubstring(String s) {
        int n = s.length();

        // 状态表
        // 记录出现位置
        int[] map = new int[1<<5]; // 1 左移5, 代表11111种状态
        Arrays.fill(map, -1); // 填充 -1,区分 00000

        map[0] = 0; // 初始状态,不包含任何元音
        int cur = 0; // 初始状态,用于推导

        int maxResult = 0;

        for (int i=0; i<n; i++){
            if(s.charAt(i)=='a'){
                cur = cur ^ (1<<0);
            }else if(s.charAt(i)=='e'){
                cur = cur ^ (1<<1);
            }else if(s.charAt(i)=='i'){
                cur = cur ^ (1<<2);
            }else if(s.charAt(i)=='o'){
                cur = cur ^ (1<<3);
            }else if(s.charAt(i)=='u'){
                cur = cur ^ (1<<4);
            }

            if(map[cur]!=-1){
                // 已存在
                // 比较长度
                maxResult = Math.max(maxResult, i - map[cur] + 1);
            }else{
                // 不存在,记录
                map[cur] = i+1;
            }
        }
        return maxResult;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值