解法一

用前缀和的思想统计从第一位到其他当前位置的各个元音字母出现的次数。然后用5位二进制数对5个元音字母的奇偶状态进行编码,放进一个List,存储当前下标。最后针对各编码的下标数组(即不同的元音字母奇偶状态)计算其下标最大值和最小值之差,即可得到满足条件的子串的长度,再从中找出最大值即为答案。

  1. import java.util.List;
  2. import java.util.Map;
  3. import java.util.HashMap;
  4. import java.util.LinkedList;
  5. class Solution {
  6. public int findTheLongestSubstring(String s) {
  7. // 防止第一位影响计算
  8. s = 'b' + s;
  9. // 统计到当前位数各个元音字母的数量
  10. int[][] count = new int[s.length()][5];
  11. // 将有相同编码的下标放在同一数组中
  12. Map<Integer, List<Integer>> indexMap = new HashMap<>();
  13. // 用于计算编码
  14. int key = 0;
  15. for (int i = 0; i < s.length(); ++i) {
  16. if (i > 0) {
  17. count[i] = count[i - 1].clone();
  18. }
  19. switch (s.charAt(i)) {
  20. case 'a':
  21. ++count[i][0];
  22. break;
  23. case 'e':
  24. ++count[i][1];
  25. break;
  26. case 'i':
  27. ++count[i][2];
  28. break;
  29. case 'o':
  30. ++count[i][3];
  31. break;
  32. case 'u':
  33. ++count[i][4];
  34. break;
  35. }
  36. // 根据各个元音字母个数奇偶情况编码
  37. key = 0;
  38. for (int j = 0; j < 5; ++j) {
  39. if (count[i][j] % 2 == 1) {
  40. key <<= 1;
  41. ++key;
  42. } else {
  43. key <<= 1;
  44. }
  45. }
  46. if (indexMap.containsKey(key)) {
  47. indexMap.get(key).add(i);
  48. } else {
  49. LinkedList<Integer> list = new LinkedList<>();
  50. list.add(i);
  51. indexMap.put(key, list);
  52. }
  53. }
  54. // 统计根据相同编码的对应下标计算满足条件的最大子串长度
  55. int maxLength = 0;
  56. for (List<Integer> list : indexMap.values()) {
  57. maxLength = Math.max(
  58. maxLength,
  59. list.get(list.size() - 1) - list.get(0)
  60. );
  61. }
  62. return maxLength;
  63. }
  64. }

解法二

解法一是自己想完做出来的,但是一提交发现时间内存都比较糟糕,再去看官方题解:https://leetcode-cn.com/problems/find-the-longest-substring-containing-vowels-in-even-counts/solution/mei-ge-yuan-yin-bao-han-ou-shu-ci-de-zui-chang-z-2/
其实前缀和和状态压缩的思想在解法一中都已经想到了,但是编码实现的方式上可以进一步提升。通过异或操作可以直接利用上一次奇偶状态,而不同编码对应的下标只需要存储最小值,也无需用复杂的Map。

  1. import java.util.Arrays;
  2. class Solution {
  3. public int findTheLongestSubstring(String s) {
  4. // 防止第一位影响计算
  5. s = 'b' + s;
  6. // 满足编码的最小下标
  7. int[] keyArr = new int[1 << 5];
  8. Arrays.fill(keyArr, -1);
  9. // 当前奇偶状态
  10. int key = 0;
  11. // 最大长度
  12. int maxLength = 0;
  13. for (int i = 0; i < s.length(); ++i) {
  14. switch (s.charAt(i)) {
  15. case 'a':
  16. key ^= 1 << 4;
  17. break;
  18. case 'e':
  19. key ^= 1 << 3;
  20. break;
  21. case 'i':
  22. key ^= 1 << 2;
  23. break;
  24. case 'o':
  25. key ^= 1 << 1;
  26. break;
  27. case 'u':
  28. key ^= 1 << 0;
  29. break;
  30. }
  31. if (keyArr[key] >= 0) {
  32. maxLength = Math.max(maxLength, i - keyArr[key]);
  33. } else {
  34. keyArr[key] = i;
  35. }
  36. }
  37. return maxLength;
  38. }
  39. }

题外

在第一遍做的时候,我一开始想用泛型数组来做不同状态的下标数组,但是由于Java泛型的实现机制,泛型数组无法使用,所以用了Map。关于这个点可以参看以下知乎讨论。