每个元音包含偶数次的最长子字符串(教你如何状态压缩)

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

你可能感兴趣的:(算法,算法,数组,前缀和,状态压缩,二元组)