前言
爲什麼引入前綴和,如何利用二進制數描述狀態,條件如何一步步等價轉化。引入哈希表存儲狀態,查找哈希表爲了尋找滿足條件的 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;
}
};