0x01.问题
给你一个字符串 s
,请你返回满足以下条件的最长子字符串的长度:每个元音字母,即 ‘a’,‘e’,‘i’,‘o’,‘u’ ,在子字符串中都恰好出现了偶数次。
示例 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
只包含小写英文字母。
来源:力扣(LeetCode)
0x02.逐层的分析问题–探索最佳解决方案
根据题目的描述,我们大致可以得出以下的关键:
-
需要求的是每个元音字母恰好是偶数个的最长子串。
- 这里需要注意的是并不一定要包含所有元音字母,只要包含了元音,且包含的都是偶数个,又是最长的,就满足了题目的要求。
-
长度达到五位数,否定了暴力枚举所有子串的方法。
接下来开始根据这些需求来寻找解决办法:
-
第一步思考: 需要得到所有子串中满足条件最长的,一般需要怎么做?
-
这个应该是非常经典的算法题型了,求子串中满足条件最长的,无非就两种办法:
- 第一种:暴力枚举所有子串的可能。在这个问题中被否定了。
- 第二种:根据具体的条件,使用前缀和,合理的使用前缀和,可以避免嵌套的遍历。
-
-
第二步思考: 既然已经确定朝着前缀和的方向去了,那么具体的前缀和表示什么呢?
- 在这个问题中,条件是:每个元音字母恰好是偶数个,这就比较麻烦了,因为元音有五个,而需要子串中每个元音都满足恰好是偶数个,每个元音字母都会影响整个条件是否满足。
- 这里所涉及的状态就有些多了,五个,暂时不能想到更好的办法。
- 所以最直接的表示出前缀和的方法就是:维护一个二维数组
pre[i][k]
表示在[0,i]
的字符串中,第k
个元音字母是否为偶数,可以用0和1表示。
-
第三步思考:前缀和灵魂发问:如何根据这个前缀和,避免嵌套的遍历?
-
我们心里都非常清楚,避免多层遍历的关键是:根据前缀和
[0,i]
的值,求出具体某个子串[j,i]
是否满足条件。 -
那么这里已知条件就是两个:
pre[i][k]
和pre[j][k]
。 -
这两个状态之间的关系,就是我们解决问题的关键了。
-
我们思考一下到底有什么关系:
pre[i][k]
表示[0,i]
的子串中第k
个元音是否为偶数。pre[j][k]
表示[0,j]
的子串中第k
个元音是否为偶数。- 我们要知道
[i,j]
这个子串是否满足,是不是只要知道这两个状态是否相等即可。因为偶数加偶数,最终是偶数,奇数加奇数,最终也是偶数。
-
到这里,前缀和的具体思路已经出来了。
-
-
第四步思考: 如何具体使用前缀和解决这个问题?
- 这已经是很经典的问题了, 无非就是使用哈希表存储每个出现的状态。
- 在这里,就是使用哈希表存储每个
pre[i][k]
,每次遇到状态已经在哈希表中出现,就取出拿来和当前最大值进行比较即可。
-
第五步思考: 最大的麻烦,五个状态如何压缩?(状态压缩的基本思路)
- 其实实际上到上步可以结束了,因为基本的思路也出来了,不过这样子编码还是会太过于复杂,为什么,因为涉及到五个状态,
pre
需要二维数组,哈希表的键也需要对应五个值。 - 如何压缩这五个状态就成为了我们的问题。
- 关于状态压缩:其实就是把一个二元状态组用二进制数来表示。选取或没选取,奇数或偶数,这些都属于二元状态。如果有NNN个选项或者NNN个数,这就是一个二元状态组了。
- 在这里,元音要么是奇数个,要么是偶数个,就是一个二元状态,有多个元音字母,就是一个二元组了。
- 需要注意的是:状态压缩适合处理有限个状态的问题,比如几个,十个左右,太多也不适用。
- 既然是用二进制数表示,在这里,我们可以给每个原因字母赋一个值,
1,2,3,4,5
,这样我们使用的时候,只需要对1
进行右移指定的位数即可。 - 在这里最多只需要
2^5=32
个状态就可以了,也就表示哈希表被优化成了常数级别。
- 其实实际上到上步可以结束了,因为基本的思路也出来了,不过这样子编码还是会太过于复杂,为什么,因为涉及到五个状态,
-
更多具体细节见代码!
0x03.解决代码–前缀和+状态压缩
class Solution {
private static final String VOWELS="aeiou";
public int findTheLongestSubstring(String s) {
Map<Integer,Integer> map=new HashMap<>();
int n=s.length();
int ans=0;
int state=0;
map.putIfAbsent(0,-1);
for(int i=0;i<n;i++){
for(int j=0;j<5;j++){
if(s.charAt(i)==VOWELS.charAt(j)){
state^=(1<<(4-j));
break;
}
}
if(map.containsKey(state)){
ans=Math.max(ans,i-map.get(state));
}
map.putIfAbsent(state,i);
}
return ans;
}
}
ATFWUS --Writing By 2020–05-20