leetcode [1371. 每个元音包含偶数次的最长子字符串]

(https://leetcode-cn.com/problems/find-the-longest-substring-containing-vowels-in-even-counts/)

方法一:暴力(虽说不太优雅,但怎么说也是自己想出来的..)

题目要求满足题意的最长子字符串,那么答案肯定是原字符串左边割掉一点(也可能不割),右边割掉一点(也可能不割),那就暴力去试,那么总共割掉几个字符呢?一个,两个.....一直到满足题意为止,假如我们设总共割掉n个,那左右怎么割呢?暴力试:左边割掉的数目一直从0到n,左边一确定,右边自然也就确定了,先看代码:

class Solution {
public:
    int findTheLongestSubstring(string s) {
       map<char,int> m; //用个map存个数
       int n = s.size();
       for(int i = 0; i < s.size(); i++) m[s[i]]++;
       if(!(m['a']&1) && !(m['e']&1) && !(m['i']&1) && !(m['o']&1) && !(m['u']&1)) return n;  //如果原字符串符合题意,那就直接输出长度
        //否则我们就尝试左右去割掉字符
       int len,left = 0,right = n-1;
       for(len = 1; len <= n; len++){ //len是总共割掉字符的数目
           int dic;//这个dic我也不知道这个是不是多此一举,这里只说一下我当时的想法
           /*因为你要往左右两边区分配割掉的总数,S表示割掉的总数,L表示左边分配的数,R表示右边分配的数		第一次 S = 1 ,我们假设把第一个首先放在左边,则有L = 1,R= 0; L = 0,R = 1;这时候你的left指针是往左走的
         第二次 注意这时候我们在上一步时已经探索到左边不割,右边割掉一个的情况,而在第二次我们只需要在右边在去掉一个字符就达到了去掉两个字符的目的,接下来进行不断尝试,这时候你的left指针是像右走的。这就是dic的意思
           */
           bool flag = false;//是否找到符合题意得情形
           if(!left){m[s[right]]--;right--;dic = 1;}//如果left指针指向0,说明接下来得尝试中left要往右走
           else {m[s[left]]--;left++;dic = -1;}//否则就行往左走
           if(!(m['a']&1) && !(m['e']&1) && !(m['i']&1) && !(m['o']&1) && !(m['u']&1)) break;
           for(int i = 1; i <= len; i++){ //左边去掉的数目
               if(dic == 1) {
                   m[s[left]]--;
                   left++;
                   right++;
                   m[s[right]]++;
               }
               else{
                   left--;
                   m[s[left]]++;
                   m[s[right]]--;
                   right--;
               }
               if(m['a']&1 || m['e']&1 || m['i']&1 || m['o']&1 || m['u']&1) continue;
               else {flag = true;break;}
           }
           if(flag) break;
       }
       return n-len; 
    }
};

方法二:前缀和+状态压缩(看题解写得)

我们可以利用前缀和得到0~x这段子列的'a' 'e' 'i' 'o' 'u'数目,那么任意一段子列的'a' 'e' 'i' 'o' 'u'数目情况都可以用两个前缀和之差得到。题目要求的状态是'a' 'e' 'i' 'o' 'u'的出现次数都为偶数的状态,那次数不是奇数就是偶数,所以说我们用五个二进制数来表示五个字母出现的奇偶性,二进制数为0表示出现次数为偶数,1就为奇数。又有奇数-奇数 = 偶数;偶数-偶数 = 偶数,所以只要二进制所有位数上的数一样,就说明两个前缀和相减得到的字串一定符合条件,我们只需要选一个最长的就行了。

class Solution {
public:
    int findTheLongestSubstring(string s) {
        int statu = 0,ans = 0;
        vector<int> v(1<<5,-1); //v[i]存的是i状态下的下标加1,为什么?因为本来初始状态state = 0的时候,数组里面应该存的是-1,但我们这次用-1当作是否被访问的条件,所以v里面就不如直接存下标加1
        v[0] = 0;
        for(int i = 0; i < s.size(); i++){
            if(s[i] == 'a') statu ^= (1<<0);
            else if(s[i] == 'e') statu ^= (1<<1);
            else if(s[i] == 'i') statu ^= (1<<2);
            else if(s[i] == 'o') statu ^= (1<<3);
            else if(s[i] == 'u') statu ^= (1<<4);

            if(v[statu] != -1){ //如果v[statu]!=-1,说明前面已经有state这个状态了,这时候我们只需要取较长的为答案就行了
                ans = max(ans,i+1-v[statu]);
            }
            else v[statu] = i+1;  
            //cout<<i<<" ";
        }
        return ans;
    }
};

总结:当遇见出现偶数字眼的时候,就要想起异或操作

猜你喜欢

转载自www.cnblogs.com/Beic233/p/12934005.html