C++前缀和-位运算-哈希表

前言

为什么引入前缀和,如何利用二进制数描述状态,条件如何一步步等价转化。引入哈希表存储状态,查找哈希表为了寻找满足条件的 pair

题目意思

给你一个字符串 s ,请你返回满足以下条件的最长子字符串的长度:每个元音字母,即 ‘a’,‘e’,‘i’,‘o’,‘u’ ,在子字符串中都恰好出现了偶数次

引入 前缀和:将双变量转为单变量

  • [i, j][i,j] 区间确定出一个子串,题目条件:[i, j][i,j] 内出现的元音均为偶数

  • 变量有 2 个,如果找齐所有区间需要两层循环

  • 其实这类“子串”、“子数组”问题,可以利用【前缀和相减】转化为:
    [0, j][0,j] 的元音次数 -− [0, i - 1][0,i−1] 对应的元音次数 == 偶数

  • 求 [0,x][0,x] 的元音次数,变量就只有 1 个了

我们只关心【差值】是不是偶数,不关心偶数等于几
奇数 - 奇数 = 偶数;偶数 - 偶数 = 偶数;一个偶数一个奇数之差一定是奇数
因此条件等价于:
[0, j][0,j] 出现的元音次数的奇偶,相同于,[0, i - 1][0,i−1] 对应的元音次数的奇偶
【特别注意】出现 0 次(没有出现),也是出现了偶次~
非奇即偶,奇偶是相对的状态

  • 两个相反、相对的状态,可以抽象为 0 和 1 —— 用 0 代表偶数,1 代表奇数
  • [0,x][0,x] 的 u o i e a 各自出现的奇偶次数,都用 0/1 表示,组成一个 5 位二进制数。譬如,00001,代表其中 u o i e 都出现偶次(包括0),a 出现奇次
  • 管这叫 [0,x][0,x] 区间的 state ,它包含 [0,x][0,x] 中 5 个元音出现次数的奇偶信息
    题目再次等价转化
    条件等价转化为:
    [0, j][0,j] 的 state 和 [0, i - 1][0,i−1] 的 state 相同,即两个二进制数相等!

遍历字符串,不断求出 [0, x][0,x] 的 state ,看看 哪些 state 是相同的,并找出位置 离得最远 的

怎么求前缀区间的 state?

  • 假设 [0,2][0,2] 的 state 是 00110 ,代表出现 e 和 i 奇数次,假如下一字符是 ii,则 [0,3][0,3] 区间出现的 ii 次数变为偶数, state 为 00010
  • 遇到 ii ,从 00110 变到 00010 ,第三位从 1 翻转为 0,其他位不变
    使特定的位翻转 正是 异或 的作用,第三位翻转,就是异或了 00100
    +异或 相当于 不带进位的二进制加法,所以有 00110 ^ 00100 = 00010
  • 元音字母 u o i e a ,分别对应了:10000、01000、00100 ……
    所以,当前前缀区间的 state 等于 前一个求出 state 异或 当前字符对应的二进制数。好比累乘、累加,只是这是 累异或.

预置 -1 的情况,使通式普遍适用

  • [i,j][i,j] 的 u o i e a 出现偶次 <=> [0, j][0,j] 的 state 相同于 [0, i - 1][0,i−1] 的 state

  • ii 显然可以为 0 ,则 i-1i−1 为 -1 ,特别地,我们让 [0,-1][0,−1] 的 state 是 00000 ,表示在字符串 -1 的位置,所有元音都出现 0 次(偶数)

  • 为了让边界情况也能套用通式,即 i= 0i=0 时,[0,j][0,j] 的元音都出现 偶次 <=> [0, j][0,j] 的 state 等于 00000,通式成立!

  • 前缀区间的 state 怎么存

  • 可以选择存到 数组 里,数组的索引和字符位置一一对应

  • 也可以用 哈希Map,存键值对
    key: state 值
    value:对应在字符串中的位置

  • 我们选择 Map ,将逐个求出的 [0, x][0,x] 的 state 存入 Map

  • 为了书写方便,转成十进制,00110 就存 6 ,是等价的

寻找满足条件的 state

  • 遍历过程中,边存 state ,要边在 Map 中查找:

  • 看看有没有 之前存过的,与当前 state 相同的 state

  • 如果有,则可能不止一个,要根据 value 值,求出它离当前位置的距离,找出有着最长距离的那个,就是我们想要的最大子串长度
    主要思路

    准备工作

  • vowel 表,对照表,一个元音对应一个二进制数

  • Map 对象,初始放入 0: -1 键值对,代表 [0,-1][0,−1] 对应的 state 为 00000 ,即十进制 0

  • state 变量,保存当前前缀区间的 state ,初始值 0

  • 遍历字符串,如果当前字符是元音,在 vowel 表获取对应的二进制数,异或 上一次求出的 state ,求出当前的 state

  • 不断往 Map 存入 state

  • 边存边查看,和当前 state 相等的,之前存过的 state 的 位置

  • 求出它与当前位置的距离,这个距离代表 满足条件的子串的长度

  • 在 遍历的过程 中,让这个距离 不断挑战 res ,如果比 res 大就更新 res

  • 最后返回 res

代码

class Solution {
public:
    int findTheLongestSubstring(string s) {
        int res = 0,dis = 0;
        /*前缀区间定义,00000代表五元音全偶*/
        int bitArray = 0;
        map<int, int> bit_to_site;
        map<int, int>::iterator iter;
        bit_to_site.insert({0,-1});
        for(int i = 0; i < s.size(); i++){
            char c = s[i];
            switch(c){
                case 'a': bitArray ^= 1; break;
                case 'e': bitArray ^= 2; break;
                case 'i': bitArray ^= 4; break;
                case 'o': bitArray ^= 8; break;
                case 'u': bitArray ^= 16; break;
                default:break;
            }
            iter = bit_to_site.find(bitArray);
            if(iter == bit_to_site.end()){
                bit_to_site.insert({bitArray, i});
            }
            dis = i - bit_to_site[bitArray];
            res = max(dis,res);
        }
        return res;
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章