一、Problem
Given the string s, return the size of the longest substring containing each vowel an even number of times. That is, ‘a’, ‘e’, ‘i’, ‘o’, and ‘u’ must appear an even number of times.
Input: s = "eleetminicoworoep"
Output: 13
Explanation: The longest substring is "leetminicowor" which contains two each of the vowels:
e, i and o and zero of the vowels: a and u.
二、Solution
方法一:位运算
一开始用双指针做的,做着做着就做不下去了,看了一眼别人的思路,居然发现是位运算…
- 用 5 个二进制位表示元音字母出现次数的种类:
- 位 0 表示元音字母出现偶数次
- 位 1 表示元音字母出现奇数次
为什么这样做?可以很方便地用异或来表示元音字母的频次的奇偶性,比如:
00001 ^ 00001 = 00000,前一个状态表示只有'a'出现奇数次,现在又出现一次'a',两个状态异或后状态为 00000
00001 ^ 00010 = 00011,上个状态是 00001,则表示a和e分别出现奇数次了
Q:状态表示有了,如果记录遍历过程中的状态呢?
A:5 个二进制位一共有 25=322^5 = 3225=32 种状态可以表示,数组 开 int[32] 即可
算法
- 定义状态:
- pos[st]pos[st]pos[st] 的值 iii 表示子串
s[0:i]
的元音字母频次状态为 ststst 时在 iii 位置
- pos[st]pos[st]pos[st] 的值 iii 表示子串
- 思考初始化:
- pos[0]=0pos[0] = 0pos[0]=0 表示元音频次状态 0 在位置 0
- 思考状态转移方程:
- 如果状态 st 没有出现过,即 pos[st]=−1pos[st] = -1pos[st]=−1,则 pos[st]=i+1pos[st] = i+1pos[st]=i+1,记录一下当前位置,供下一次使用。
- 否则该状态必定经过 >= 2 次,此时可以推断出当前状态再次出现,什么情况下状态才会再次出现?肯定是又遇到了遇到过的元音字母啦,故此时可记录临时答案与 max 中:max=i+1−pos[st]max = i+1-pos[st]max=i+1−pos[st]
class Solution {
public int findTheLongestSubstring(String S) {
char s[] = S.toCharArray(), v[] = new char[] {'a', 'e', 'i', 'o', 'u'};
int max = 0, st = 0, pos[] = new int[1<<5];
Arrays.fill(pos, -1);
pos[0] = 0;
for (int i = 0; i < s.length; i++) {
for (int k = 0; k < v.length; k++)
if (s[i] == v[k]) {
st ^= 1 << k;
break;
}
if (pos[st] == -1) pos[st] = i + 1;
else max = Math.max(max, i + 1 - pos[st]);
}
return max;
}
}
复杂度分析
- 时间复杂度:O(n)O(n)O(n),
- 空间复杂度:O(1)O(1)O(1),