leetcode 1371 包含偶数个元音字母的子字符串

给定一个字符串,寻找一个最长子字符串,使得包含的原因字母都是偶数个。

我们很容易想到,暴力枚举。然后通过一点点巧妙的减支,可以通过。

首先扫一遍字符串,统计每个字符出现的次数。实际上我们只关注元音字母,不过直接全部统计比较方便(我估计时间上也会快一点)

然后我们枚举 i ~ j ,i 从0 到 len , j 从 len到 i。然后判断 i j组成的子字符串是否符合要求。

注意两个剪枝条件: 如果 i - len 的长度小于已知的最大值,则 结束。

如果 i - j 的距离小于已知的最大值,则结束。

public static int findTheLongestSubstring(String s) {
        int maxLength = -1;
        int[] count = new int[26];
        int len = s.length();
        for(int i=0;i<len;i++){
            count[s.charAt(i)-'a']++;
        }
        char[] Vowels = new char[]{'a','e','i','o','u'};
        int vowelsEvenNum = 0;
        for(int i=0;i<5;i++){
            if(count[Vowels[i]-'a']%2==0) vowelsEvenNum++;
        }
        if(vowelsEvenNum==5) return len;
        for(int i=0;i<len;i++){
            if(len-i<maxLength) break;
            if(i>0) count[s.charAt(i-1)-'a']--;
            int[] tmp = count.clone();
            for(int j=len-1;j>=i;j--){
                if(maxLength>j-i+1) break;
                if(j<len-1) tmp[s.charAt(j+1)-'a']--;
                vowelsEvenNum=0;
                for(int k=0;k<5;k++){
                    if(tmp[Vowels[k]-'a']%2==0) vowelsEvenNum++;
                }
                if(vowelsEvenNum==5){
                    maxLength=Math.max(maxLength,j-i+1);
                }


            }
        }
        return maxLength;
View Code

 

我们优化一下。首先想到前缀和。 我们用 pre[i][k]表示第i个字符前,字符k有多少个。

则我们的子字符串满足:对于任意的k pre[i][k] - pre[j][k] 是偶数。同样的枚举 i , j 。 我估计上面的剪枝同样有效。

但是有新的优化,我们小学数学学过:

奇数-奇数=偶数

偶数-偶数=偶数

所以我们其实不用记录每个字符出现的个数,只需记录每个字符的奇偶性就可以了。这样的话,我们设计一个特殊的key:

{ a:,e: ,i: ,o:, u:} *:表示*字符的奇偶性。我们利用这样的特殊结构当做key,存储它第一次出现的位置。我们就可以利用哈希表通过一次遍历解决。

大概代码是这样的:

        Map<STA, Integer> poss = new HashMap<>();

        for (int i = 0; i < s.length(); i++) {
            STA sta = new STA();
            if (s.charAt(i) == 'a') sta.a ^= 1;
            if (s.charAt(i) == 'e') sta.e ^= 1;
            if (s.charAt(i) == 'i') sta.i ^= 1;
            if (s.charAt(i) == 'o') sta.o ^= 1;
            if (s.charAt(i) == 'u') sta.u ^= 1;
            if (poss.containsKey(sta)) {
                ans = Math.max(ans, i - poss.get(sta) + 1);
            } else {
                poss.put(sta, i);
            }    

 

这里我们要新加一个STA的类,而且要重写equal和hashCode方法。不太友好。其实我们只是用到了5个字母。我们用二进制来表示这个5个字母的奇偶性。1表示奇数个,0表示偶数个。我们只需要一个1<<5长度的数组就可以罗列所有状态(出现了出现了,有一个装逼利器:状态压缩)

这里有个编程的技巧。我们把00000,即所有字母都是偶数个的状态的位置设为0. 其他状态的位置设置为 i+1 .。这是符合逻辑的,因为一个都不选,长度为0,所有的元音字母的个数都为偶数。

然后这样的话,当一个状态出现两次的时候,它的长度就是 i+1 - pos[status],这样就不用考虑边界值的问题了。

public static int findTheLongestSubstring2(String s) {
        int ans = 0;
        int[] pos = new int[1 << 5];
        int status = 0;
        Arrays.fill(pos, -1);
        char[] sChar = s.toCharArray();
        int len = s.length();
        pos[0] = 0;
        for (int i = 0; i < len; i++) {
            char c = s.charAt(i);
            if (c == 'a') status ^= 1 << 0;
            if (c == 'e') status ^= 1 << 1;
            if (c == 'i') status ^= 1 << 2;
            if (c == 'o') status ^= 1 << 3;
            if (c == 'u') status ^= 1 << 4;
            if (pos[status] >= 0)
                ans = Math.max(ans, i + 1 - pos[status]);
            else
                pos[status] = i + 1;
        }
       return ans;
}
View Code

 

posted @ 2020-05-20 15:56  超级学渣渣  阅读(426)  评论(0编辑  收藏  举报