|
2 | 2 | // #2025_04_01_Time_13_ms_(97.44%)_Space_63.70_MB_(46.03%) |
3 | 3 |
|
4 | 4 | function findSubstring(s: string, words: string[]): number[] { |
5 | | - let ans: number[] = [] |
6 | | - let n1 = words[0].length |
7 | | - let n2 = s.length |
8 | | - let map1 = new Map<string, number>() |
9 | | - for (let ch of words) { |
10 | | - map1.set(ch, (map1.get(ch) ?? 0) + 1) |
| 5 | + if (words.length === 0) return [] |
| 6 | + |
| 7 | + const result: number[] = [] |
| 8 | + const wordLen = words[0].length |
| 9 | + const totalLen = words.length * wordLen |
| 10 | + const limit = s.length |
| 11 | + |
| 12 | + const target = buildFreqMap(words) |
| 13 | + |
| 14 | + for (let offset = 0; offset < wordLen; offset++) { |
| 15 | + scanWindow(s, offset, wordLen, totalLen, limit, target, result) |
11 | 16 | } |
12 | | - for (let i = 0; i < n1; i++) { |
13 | | - let left = i |
14 | | - let j = i |
15 | | - let c = 0 |
16 | | - let map2 = new Map<string, number>() |
17 | | - while (j + n1 <= n2) { |
18 | | - let word1 = s.substring(j, j + n1) |
19 | | - j += n1 |
20 | | - if (map1.has(word1)) { |
21 | | - map2.set(word1, (map2.get(word1) ?? 0) + 1) |
22 | | - c++ |
23 | | - while ((map2.get(word1) ?? 0) > (map1.get(word1) ?? 0)) { |
24 | | - let word2 = s.substring(left, left + n1) |
25 | | - map2.set(word2, (map2.get(word2) ?? 0) - 1) |
26 | | - left += n1 |
27 | | - c-- |
28 | | - } |
29 | | - if (c === words.length) { |
30 | | - ans.push(left) |
31 | | - } |
32 | | - } else { |
33 | | - map2.clear() |
34 | | - c = 0 |
35 | | - left = j |
36 | | - } |
| 17 | + |
| 18 | + return result |
| 19 | +} |
| 20 | + |
| 21 | +function buildFreqMap(words: string[]): Map<string, number> { |
| 22 | + const map = new Map<string, number>() |
| 23 | + for (const w of words) { |
| 24 | + map.set(w, (map.get(w) ?? 0) + 1) |
| 25 | + } |
| 26 | + return map |
| 27 | +} |
| 28 | + |
| 29 | +function scanWindow( |
| 30 | + s: string, |
| 31 | + start: number, |
| 32 | + wordLen: number, |
| 33 | + totalLen: number, |
| 34 | + limit: number, |
| 35 | + target: Map<string, number>, |
| 36 | + result: number[], |
| 37 | +): void { |
| 38 | + let left = start |
| 39 | + let count = 0 |
| 40 | + const window = new Map<string, number>() |
| 41 | + |
| 42 | + for (let right = start; right + wordLen <= limit; right += wordLen) { |
| 43 | + const word = s.substring(right, right + wordLen) |
| 44 | + |
| 45 | + if (!target.has(word)) { |
| 46 | + window.clear() |
| 47 | + count = 0 |
| 48 | + left = right + wordLen |
| 49 | + continue |
37 | 50 | } |
| 51 | + |
| 52 | + window.set(word, (window.get(word) ?? 0) + 1) |
| 53 | + count++ |
| 54 | + |
| 55 | + shrinkWindowIfNeeded(s, word, window, target, wordLen, () => { |
| 56 | + const removed = s.substring(left, left + wordLen) |
| 57 | + window.set(removed, (window.get(removed) ?? 0) - 1) |
| 58 | + left += wordLen |
| 59 | + count-- |
| 60 | + }) |
| 61 | + |
| 62 | + if (count * wordLen === totalLen) { |
| 63 | + result.push(left) |
| 64 | + } |
| 65 | + } |
| 66 | +} |
| 67 | + |
| 68 | +function shrinkWindowIfNeeded( |
| 69 | + _s: string, |
| 70 | + word: string, |
| 71 | + window: Map<string, number>, |
| 72 | + target: Map<string, number>, |
| 73 | + _wordLen: number, |
| 74 | + shrink: () => void, |
| 75 | +): void { |
| 76 | + while ((window.get(word) ?? 0) > (target.get(word) ?? 0)) { |
| 77 | + shrink() |
38 | 78 | } |
39 | | - return ans |
40 | 79 | } |
41 | 80 |
|
42 | 81 | export { findSubstring } |
0 commit comments