Hi 你們好,我是張小豬。歡迎來到『寶寶也能看懂』系列之 leetcode 題解。git
這裏是第 168 期的第 3 題,也是題目列表中的第 1297 題 -- 『Maximum Number of Occurrences of a Substring』github
Given a string s
, return the maximum number of ocurrences of any substring under the following rules:shell
The number of unique characters in the substring must be less than or equal to maxLetters
.
The substring size must be between minSize
and maxSize
inclusive.less
Example 1:post
Input: s = "aababcaab", maxLetters = 2, minSize = 3, maxSize = 4 Output: 2 Explanation: Substring "aab" has 2 ocurrences in the original string. It satisfies the conditions, 2 unique letters and size 3 (between minSize and maxSize).
Example 2:優化
Input: s = "aaaa", maxLetters = 1, minSize = 3, maxSize = 3 Output: 2 Explanation: Substring "aaa" occur 2 times in the string. It can overlap.
Example 3:spa
Input: s = "aabcabcab", maxLetters = 2, minSize = 2, maxSize = 3 Output: 3
Example 4:code
Input: s = "abcde", maxLetters = 2, minSize = 3, maxSize = 3 Output: 0
Constraints:ip
1 <= s.length <= 10^5 1 <= maxLetters <= 26 1 <= minSize <= maxSize <= min(26, s.length) s only contains lowercase English letters.
MEDIUMleetcode
乍一看題目內容彷佛還挺複雜,僅輸入參數就有 4 個。細看一下,描述的過程爲,嘗試把輸入的字符串 s
進行拆分,對子字符串的要求爲最多包含 maxLetters
個不一樣的字符,而且長度要在 [minSize, maxSize]
這個區間裏。每一個這樣的子字符串可能會在 s
中出現一次或者屢次。須要返回最大的子字符串出現次數。
結合題目內容和例子,咱們能夠發現一件事情。假設咱們子字符串的長度範圍爲 [2,4],而且容許的最大不一樣字符數爲 4,那麼全部知足需求的長度爲 4 的子字符串,它的每一次重複裏必定至少包含一次長度爲 2 的子字符串。例如對於字符串 "abcdefghabcd",其中 "abcd" 重複了兩次,那麼至少 "ab" 也重複了兩次。
因爲咱們是爲了求得最大的子字符串出現的次數,因此根據上面發現的事情,咱們不可貴到其實咱們只須要去找出知足條件要求的最短的子字符串,而後完成計數便可。由於比它更長的子字符串的出現次數必定是小於或者等於它的。
基於咱們上述的思路,最直接的想法是,咱們能夠從頭開始遍歷整個原始字符串,找到每一個符合要求的最短的子字符串。而後利用 Map 結構完成計數。最終獲得最大的結果。代碼以下:
const maxFreq = (s, maxLetters, minSize, maxSize) => { const substrMap = new Map(); let max = 0; outer: for (let i = 0; i <= s.length - minSize; ++i) { const substr = s.substr(i, minSize); const letterSet = new Set(); for (const char of substr) { letterSet.add(char); if (letterSet.size > maxLetters) continue outer; } const count = substrMap.has(substr) ? substrMap.get(substr) + 1 : 1; substrMap.set(substr, count); count > max && (max = count); } return max; };
這段代碼在 Accepted 後我跑到了 112ms 的結果。其中用到了不太常見的 label,你們若是不喜歡的能夠在內部循環後,添加一個判斷用以直接跳出便可。
比較慚愧的說,這個優化其實並不來源於我本身。在上述方案提交後,我從新看了一下題目的描述,在關聯話題裏我看到了 Bit Manipulation 這一項。再結合原始字符串 s
只會包含小寫英文字母。因而被提醒到了這一個優化點。
上述方案裏,咱們判斷子字符串中不一樣字符出現的數量,是經過遍歷結合 Set
的方式實現的,循環中每次都會執行集合操做。可是因爲小寫英文字母一共只有 26 個,而每一個字母的狀態也只有兩種,即出現或未出現。因而咱們能夠聯想到用 0 和 1 這兩個值標識這兩種狀態,而且自然的咱們的數字在計算機中就是二進制來表示,因而咱們能夠嘗試用 32 位的 Int 直接標識出子字符串中全部的字母狀態。
利用這個思路,咱們能夠省下不少集合的操做,代價只是簡單的位運算。與此同時,空間上來看也有很大的優點。代碼以下:
const maxFreq = (s, maxLetters, minSize, maxSize) => { const substrMap = new Map(); let max = 0; outer: for (let i = 0; i <= s.length - minSize; ++i) { const substr = s.substr(i, minSize); let flag = len = 0; for (let i = 0; i < substr.length; ++i) { const inc = 1 << (substr.charCodeAt(i) - 97); if ((flag & inc) === 0 && ++len > maxLetters) continue outer; flag |= inc; } const count = substrMap.has(substr) ? substrMap.get(substr) + 1 : 1; substrMap.set(substr, count); count > max && (max = count); } return max; };
其中 label 的問題和上面同樣能夠隨意替換。這段代碼我跑到了 68ms,暫時 beats 100%。因而先湊合着這樣了。