@@ -33,41 +33,38 @@ thrice_.
3333A ** substring** is a contiguous ** non-empty** sequence of characters within a
3434string.
3535
36-
37-
3836** Example 1:**
3937
4038> Input: s = "aaaa"
41- >
39+ >
4240> Output: 2
43- >
41+ >
4442> Explanation: The longest special substring which occurs thrice is "aa": substrings "_ ** aa** _ aa", "a _ ** aa** _ a", and "aa _ ** aa** _ ".
45- >
43+ >
4644> It can be shown that the maximum length achievable is 2.
4745
4846** Example 2:**
4947
5048> Input: s = "abcdef"
51- >
49+ >
5250> Output: -1
53- >
51+ >
5452> Explanation: There exists no special substring which occurs at least thrice. Hence return -1.
5553
5654** Example 3:**
5755
5856> Input: s = "abcaba"
59- >
57+ >
6058> Output: 1
61- >
59+ >
6260> Explanation: The longest special substring which occurs thrice is "a": substrings "_ ** a** _ bcaba", "abc _ ** a** _ ba", and "abcab _ ** a** _ ".
63- >
61+ >
6462> It can be shown that the maximum length achievable is 1.
6563
6664** Constraints:**
6765
68- * ` 3 <= s.length <= 50 `
69- * ` s ` consists of only lowercase English letters.
70-
66+ - ` 3 <= s.length <= 50 `
67+ - ` s ` consists of only lowercase English letters.
7168
7269## 题目大意
7370
@@ -80,82 +77,279 @@ string.
8077
8178** 子字符串** 是字符串中的一个连续** 非空** 字符序列。
8279
83-
84-
8580** 示例 1:**
8681
87- >
88- >
89- >
90- >
91- >
9282> ** 输入:** s = "aaaa"
93- >
83+ >
9484> ** 输出:** 2
95- >
85+ >
9686> ** 解释:** 出现三次的最长特殊子字符串是 "aa" :子字符串 "_ ** aa** _ aa"、"a _ ** aa** _ a" 和 "aa _ ** aa** _ "。
97- >
87+ >
9888> 可以证明最大长度是 2 。
99- >
100- >
10189
10290** 示例 2:**
10391
104- >
105- >
106- >
107- >
108- >
10992> ** 输入:** s = "abcdef"
110- >
93+ >
11194> ** 输出:** -1
112- >
95+ >
11396> ** 解释:** 不存在出现至少三次的特殊子字符串。因此返回 -1 。
114- >
115- >
11697
11798** 示例 3:**
11899
119- >
120- >
121- >
122- >
123- >
124100> ** 输入:** s = "abcaba"
125- >
101+ >
126102> ** 输出:** 1
127- >
103+ >
128104> ** 解释:** 出现三次的最长特殊子字符串是 "a" :子字符串 "_ ** a** _ bcaba"、"abc _ ** a** _ ba" 和 "abcab _ ** a** _ "。
129- >
105+ >
130106> 可以证明最大长度是 1 。
131- >
132- >
133107
108+ ** 提示:**
134109
110+ - ` 3 <= s.length <= 50 `
111+ - ` s ` 仅由小写英文字母组成。
135112
136- ** 提示: **
113+ ## 解题思路
137114
138- * ` 3 <= s.length <= 50 `
139- * ` s ` 仅由小写英文字母组成。
115+ ### 思路一:暴力遍历
140116
117+ 1 . ** 遍历子串长度:**
118+ - 从最长可能的长度开始遍历,逐步检查是否存在符合条件的特殊子串。
119+ 2 . ** 检查特殊字符串:**
141120
142- ## 解题思路
121+ - 使用 ` new Set(sub).size ` 检查子串是否由单一字符组成。
122+ - 如果 ` size === 1 ` ,表示子串为特殊字符串。
123+
124+ 3 . ** 统计子串出现次数:**
125+
126+ - 利用 ` Map ` 数据结构记录每个特殊子串的出现次数。
127+ - 遍历完成后,检查是否有子串的出现次数达到 3 次以上。
128+
129+ 4 . ** 返回结果:**
130+ - 如果找到满足条件的子串,返回其长度。
131+ - 如果没有找到,返回 ` -1 ` 。
143132
144133#### 复杂度分析
145134
146- - ** 时间复杂度** :` O() ` ,
147- - ** 空间复杂度** :` O() ` ,
135+ - ** 时间复杂度:** ` O(n^3) `
136+
137+ - 外层循环遍历可能的子串长度,从最大值逐步减少,最多为 ` O(n) ` 。
138+ - 内层遍历所有子串,最多为 ` O(n^2) ` 。
139+ - 检查特殊字符串的过程为 ` O(k) ` ,其中 ` k ` 为子串长度。
140+ - 总时间复杂度为 ` O(n^3) ` 。
141+
142+ - ** 空间复杂度:** ` O(n^2) `
143+ - 使用 ` Map ` 记录子串的出现次数,最差情况下存储所有子串,空间复杂度为 ` O(n^2) ` 。
144+
145+ 这段代码效率较低,对于较大输入可能会超时,可以进一步优化。
146+
147+ ---
148+
149+ ### 思路二:双指针
150+
151+ 1 . ** 定义判断函数 ` isValid ` **
152+
153+ 用一个辅助函数 ` isValid(len) ` 检查是否存在长度为 ` len ` 的特殊子字符串,且该子字符串至少出现 3 次。
154+
155+ 具体逻辑如下:
156+
157+ - 使用一个 ` count ` 数组,记录每个字符的特殊子字符串出现次数。
158+ - 使用双指针 ` p, i ` ,维护子字符串的起始和结束位置:
159+ - 移动 ` p ` 直到 ` s[i] === s[p] ` ,从而找到以 ` s[i] ` 为单一字符组成的连续子字符串的起点。
160+ - 如果当前子字符串长度 ` i - p + 1 >= len ` ,说明可以把这段子字符串作为长度为 ` len ` 的特殊子字符串,增加计数。
161+ - 如果任意字符的计数达到 3,则返回 ` true ` ,说明存在满足条件的子字符串。
162+
163+ 2 . ** 从大到小尝试长度**
164+
165+ 由于我们要找的是最长的特殊子字符串长度,因此:
166+
167+ 1 . 从最大可能长度 ` s.length - 2 ` 开始尝试,逐渐减小长度。
168+ 2 . 对于每一个长度 ` len ` ,调用 ` isValid(len) ` 检查是否满足条件:
169+
170+ - 如果满足,直接返回 ` len ` 。
171+ - 否则继续尝试更短的长度,直到 ` len = 1 ` 。
172+
173+ 3 . ** 终止条件**
174+
175+ 如果遍历完所有长度都不满足,返回 ` -1 ` 。
176+
177+ #### 复杂度分析
178+
179+ - ** 时间复杂度:** ` O(n^2) `
180+
181+ - 外层循环从最大长度到最小长度,最多进行 ` O(n) ` 次。
182+ - 内部 ` isValid ` 函数,双指针扫描整个字符串,每个字符最多访问两次,复杂度为 ` O(n) ` 。
183+ - 总时间复杂度为 ` O(n^2) ` 。
184+
185+ - ** 空间复杂度:** ` O(1) `
186+ - 使用 ` count ` 数组记录每个字符的特殊子字符串出现次数,大小固定为 26,空间复杂度为 ` O(1) ` 。
187+ - 其他变量占用常量空间。
188+
189+ ---
190+
191+ ### 思路三:二分查找 + 双指针
192+
193+ 在思路二的基础上,我们可以使用二分查找确定最长特殊子字符串的长度:
194+
195+ - 初始范围为 ` 1 ` 到字符串长度 ` s.length ` 。
196+ - 通过中点 ` mid ` 分割:
197+ - 如果长度为 ` mid ` 的特殊子字符串满足条件,则更新左边界 ` left = mid - 1 ` 。
198+ - 否则更新右边界 ` right = mid + 1 ` 。
199+ - 二分结束后,` right ` 即为满足条件的最大长度,注意不是 ` left ` ,因为在每次迭代中都排除了 ` mid ` ,而 ` mid ` 本身也可以是答案之一。
200+ - 特殊情况处理:如果长度为 1 的特殊子字符串都不存在(即 ` isValid(1) ` 返回 ` false ` ),直接返回 ` -1 ` 。
201+
202+ #### 复杂度分析
203+
204+ - ** 时间复杂度:** ` O(n * log n) `
205+
206+ - 二分查找,复杂度为 ` O(log n) ` 。
207+ - 内部 ` isValid ` 函数,双指针扫描整个字符串,每个字符最多访问两次,复杂度为 ` O(n) ` 。
208+ - 总时间复杂度为 ` O(n * log n) ` 。
209+
210+ - ** 空间复杂度:** ` O(1) `
211+ - 使用 ` count ` 数组记录每个字符的特殊子字符串出现次数,大小固定为 26,空间复杂度为 ` O(1) ` 。
212+ - 其他变量占用常量空间。
148213
149214## 代码
150215
216+ ::: code-tabs
217+
218+ @tab 暴力遍历
219+
151220``` javascript
221+ /**
222+ * @param {string} s
223+ * @return {number}
224+ */
225+ var maximumLength = function (s ) {
226+ let minRange = 1 ,
227+ maxRange = s .length - 2 ;
228+
229+ for (let len = maxRange; len >= minRange; len-- ) {
230+ let map = new Map ();
231+
232+ // 遍历所有长度为 len 的子串
233+ for (let j = 0 ; j <= s .length - len; j++ ) {
234+ let sub = s .substring (j, j + len);
235+
236+ // 检查子串是否是特殊字符串(由单一字符组成)
237+ if (new Set (sub).size === 1 ) {
238+ map .set (sub, (map .get (sub) || 0 ) + 1 );
239+ }
240+ }
241+
242+ // 检查是否有子串出现至少 3 次
243+ for (let key of map .keys ()) {
244+ if (map .get (key) >= 3 ) {
245+ return len;
246+ }
247+ }
248+ }
249+
250+ return - 1 ;
251+ };
252+ ```
253+
254+ @tab 双指针
152255
256+ ``` javascript
257+ /**
258+ * @param {string} s
259+ * @return {number}
260+ */
261+ var maximumLength = function (s ) {
262+ // 判断长度为 len 的特殊子字符串是否满足条件
263+ const isValid = (len ) => {
264+ let count = new Array (26 ).fill (0 );
265+ let p = 0 ;
266+ for (let i = 0 ; i < s .length ; i++ ) {
267+ const char = s[i].charCodeAt () - ' a' .charCodeAt ();
268+ // 移动指针 p,确保子字符串以单一字符组成
269+ while (s[i] !== s[p]) p++ ;
270+ // 如果当前子字符串长度 >= len,增加计数
271+ if (i - p + 1 >= len) {
272+ count[char]++ ;
273+ }
274+ // 如果出现至少 3 次,返回 true
275+ if (count[char] >= 3 ) {
276+ return true ;
277+ }
278+ }
279+ // 未找到符合条件的子字符串
280+ return false ;
281+ };
282+
283+ let minRange = 1 ,
284+ maxRange = s .length - 2 ;
285+
286+ // 从大到小尝试长度
287+ for (let len = maxRange; len >= minRange; len-- ) {
288+ if (isValid (len)) return len;
289+ }
290+
291+ // 如果没有找到,返回 -1
292+ return - 1 ;
293+ };
294+ ```
295+
296+ @tab 二分查找 + 双指针
297+
298+ ``` javascript
299+ /**
300+ * @param {string} s
301+ * @return {number}
302+ */
303+ var maximumLength = function (s ) {
304+ // 判断长度为 len 的特殊子字符串是否满足条件
305+ const isValid = (len ) => {
306+ let count = new Array (26 ).fill (0 );
307+ let p = 0 ;
308+ for (let i = 0 ; i < s .length ; i++ ) {
309+ const char = s[i].charCodeAt () - ' a' .charCodeAt ();
310+ // 移动指针 p,确保子字符串以单一字符组成
311+ while (s[i] !== s[p]) p++ ;
312+ // 如果当前子字符串长度 >= len,增加计数
313+ if (i - p + 1 >= len) {
314+ count[char]++ ;
315+ }
316+ // 如果出现至少 3 次,返回 true
317+ if (count[char] >= 3 ) {
318+ return true ;
319+ }
320+ }
321+ // 未找到符合条件的子字符串
322+ return false ;
323+ };
324+
325+ // 特殊情况处理:如果长度为 1 的特殊子字符串都不存在,直接返回 -1
326+ if (! isValid (1 )) return - 1 ;
327+
328+ // 二分查找
329+ let left = 1 ,
330+ right = s .length ;
331+
332+ while (left <= right) {
333+ const mid = ((left + right) / 2 ) | 0 ;
334+ if (isValid (mid)) {
335+ // 更新左边界
336+ left = mid + 1 ;
337+ } else {
338+ // 更新右边界
339+ right = mid - 1 ;
340+ }
341+ }
342+
343+ return right;
344+ };
153345```
154346
347+ :::
348+
155349## 相关题目
156350
157351<!-- prettier-ignore -->
158352| 题号 | 标题 | 题解 | 标签 | 难度 | 力扣 |
159353| :------: | :------ | :------: | :------ | :------: | :------: |
160354| 3 | 无重复字符的最长子串 | [[ ✓]] ( /problem/0003.md ) | [ ` 哈希表 ` ] ( /tag/hash-table.md ) [ ` 字符串 ` ] ( /tag/string.md ) [ ` 滑动窗口 ` ] ( /tag/sliding-window.md ) | 🟠 | [ 🀄️] ( https://leetcode.cn/problems/longest-substring-without-repeating-characters ) [ 🔗] ( https://leetcode.com/problems/longest-substring-without-repeating-characters ) |
161- | 395 | 至少有 K 个重复字符的最长子串 | | [ ` 哈希表 ` ] ( /tag/hash-table.md ) [ ` 字符串 ` ] ( /tag/string.md ) [ ` 分治 ` ] ( /tag/divide-and-conquer.md ) ` 1+ ` | 🟠 | [ 🀄️] ( https://leetcode.cn/problems/longest-substring-with-at-least-k-repeating-characters ) [ 🔗] ( https://leetcode.com/problems/longest-substring-with-at-least-k-repeating-characters ) |
355+ | 395 | 至少有 K 个重复字符的最长子串 | | [ ` 哈希表 ` ] ( /tag/hash-table.md ) [ ` 字符串 ` ] ( /tag/string.md ) [ ` 分治 ` ] ( /tag/divide-and-conquer.md ) ` 1+ ` | 🟠 | [ 🀄️] ( https://leetcode.cn/problems/longest-substring-with-at-least-k-repeating-characters ) [ 🔗] ( https://leetcode.com/problems/longest-substring-with-at-least-k-repeating-characters ) |
0 commit comments