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;
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章