【leetcode】5337. 每个元音包含偶数次的最长子字符串( Find the Longest Substring Containing Vowels in Even Counts)


题目描述

【leetcode】5337. 每个元音包含偶数次的最长子字符串( Find the Longest Substring Containing Vowels in Even Counts)

给你一个字符串 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 只包含小写英文字母。

第一次解答

思路
暴力法。滑动窗口,窗口长度分别设置为1,2,3,…
毫无疑问最后超时了,所以也不确定代码对不对。

代码

class Solution {
pu

blic:
    bool isEven(unsigned long *count){
        return (count['a'] % 2 == 0 && count['e'] % 2 == 0 &&
                count['i'] % 2 == 0 && count['o'] % 2 == 0 &&
                count['u'] % 2 == 0
               );
    }
    int findTheLongestSubstring(string s) {
        int max_length = 0;
        if(s.size() == 0)
            return 0;
        
        // 滑动窗口
        int window_length = s.size();
        while(window_length > 0){
            int window_start = 0;
            // 怕麻烦,直接把asscii码都申请了
            unsigned long count[128] = {0};
            memset(&count[0], 0, 128*sizeof(unsigned long));
            for(int window_end=0; window_end<s.size(); window_end++){
                count[s[window_end]] += 1;
                if(window_end - window_start + 1 >= window_length){
                    bool is_even = isEven(&count[0]);
                    count[s[window_start]] -= 1;
                    window_start++;
                    if(!is_even){
                        continue;
                    }
                    if(window_length > max_length){
                        max_length = window_length;
                        break;// 在该长度下找到了,就遍历更大的长度
                    }
                }
            }
            
            
            window_length--;
        }
        
        return max_length;
    }
};

结果:
超时。

第二次解答

思路
看了题解。
我们知道,一个数加上偶数不改变奇偶性,例如奇数+偶数=奇数,偶数+偶数=偶数。

题目要求是5个元音字母出现偶数次的最长区间,为了便于说明,我们先仅考虑字母a出现偶数次数的最长区间,我们假设这个满足条件的区间为[left, right]。[0, left]与[left, right]可组成区间[0, right],记这三个区间字母a个数分别为L、K和R。
其中K为偶数时,[left, right]为我们要找的区间。根据上面所说的,偶数不改变奇偶性,所以K不改变L和R的奇偶性,因此L和R的奇偶性相同。
反过来说,当L和R的奇偶性相同时,K就为偶数,(left, right]就是我们要找的区间,right - left就是区间长度。
以上结论是仅考虑字母a出现偶数次数的最长区间得到的,当问题扩展为5个元音字母出现偶数次的最长区间也有类似结论。

回到本题,考虑5个元音字母的奇偶性时,存在2^5=32种奇偶性状态(只用到了5bits,每个bit表示一个元音字母出现的奇偶性,0表示对应字母出现偶数次,1代表对应字母出现奇数次),我们记录每一种状态第一次出现的序号,相当于记录left,在后续过程中若出现相同状态则表明遇到了区间(left, right],计算此时的区间长度,若大于最大长度,则更新最大长度。

注意:本题能用这种方法的前提是一个数加上偶数不改变奇偶性,所以当题目改为5个元音字母出现奇数次的最长区间时,这种方法就不适用了。但是我们可以转换一下题目,使得方法适用于题目,我们可以把题目看为除了5个元音字母外的字母,出现偶数次的最长区间,这样,就还可以使用本文的方法。

test case:
“eleetminicoworoep”
“leetcodeisgreat”
“bcbcbc”

代码:
注意:这里的curr_state默认为0,因为这时候确实没有任何元音字母,这个状态第一次的序号应为-1。若不这样做,计算(left, right]半开半闭区间的长度right-left时,当这个区间是在默认状态下找到时,会出错。出错的测试用例为:“leetcodeisgreat”

class Solution {
public:
    int findTheLongestSubstring(string s) {
        vector<int> status(1<<5, -2);// 默认所有状态的left都为-2,表示还没遇到第一次出现的序号
        int curr_state = 0; // 默认状态为全0,5bits,每个bit表示一个元音字母出现的奇偶性,0表示偶数次
        status[curr_state] = -1;// 注意:默认状态出现的序号为-1处。
        int max_length = 0;
        for(int i=0; i<s.size(); ++i){
            // 计算当前状态
            switch(s[i]){
                case 'a':curr_state ^= 1<<0;break;
                case 'e':curr_state ^= 1<<1;break;
                case 'i':curr_state ^= 1<<2;break;
                case 'o':curr_state ^= 1<<3;break;
                case 'u':curr_state ^= 1<<4;break;
                default:break;
            }
            // 若状态第一次出现,记录出现的序号
            if(-2 == status[curr_state]){
                status[curr_state] = i;
            }
            // 否则,表示再次遇到同状态,找到了区间(left, right],更新最大长度
            else{
                max_length = max(max_length, i - status[curr_state]);
            }

        }
        return max_length;
    }
};

结果:

截图

相关/参考链接

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值