@@ -33,41 +33,38 @@ thrice_.
33
33
A ** substring** is a contiguous ** non-empty** sequence of characters within a
34
34
string.
35
35
36
-
37
-
38
36
** Example 1:**
39
37
40
38
> Input: s = "aaaa"
41
- >
39
+ >
42
40
> Output: 2
43
- >
41
+ >
44
42
> Explanation: The longest special substring which occurs thrice is "aa": substrings "_ ** aa** _ aa", "a _ ** aa** _ a", and "aa _ ** aa** _ ".
45
- >
43
+ >
46
44
> It can be shown that the maximum length achievable is 2.
47
45
48
46
** Example 2:**
49
47
50
48
> Input: s = "abcdef"
51
- >
49
+ >
52
50
> Output: -1
53
- >
51
+ >
54
52
> Explanation: There exists no special substring which occurs at least thrice. Hence return -1.
55
53
56
54
** Example 3:**
57
55
58
56
> Input: s = "abcaba"
59
- >
57
+ >
60
58
> Output: 1
61
- >
59
+ >
62
60
> Explanation: The longest special substring which occurs thrice is "a": substrings "_ ** a** _ bcaba", "abc _ ** a** _ ba", and "abcab _ ** a** _ ".
63
- >
61
+ >
64
62
> It can be shown that the maximum length achievable is 1.
65
63
66
64
** Constraints:**
67
65
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.
71
68
72
69
## 题目大意
73
70
@@ -80,82 +77,279 @@ string.
80
77
81
78
** 子字符串** 是字符串中的一个连续** 非空** 字符序列。
82
79
83
-
84
-
85
80
** 示例 1:**
86
81
87
- >
88
- >
89
- >
90
- >
91
- >
92
82
> ** 输入:** s = "aaaa"
93
- >
83
+ >
94
84
> ** 输出:** 2
95
- >
85
+ >
96
86
> ** 解释:** 出现三次的最长特殊子字符串是 "aa" :子字符串 "_ ** aa** _ aa"、"a _ ** aa** _ a" 和 "aa _ ** aa** _ "。
97
- >
87
+ >
98
88
> 可以证明最大长度是 2 。
99
- >
100
- >
101
89
102
90
** 示例 2:**
103
91
104
- >
105
- >
106
- >
107
- >
108
- >
109
92
> ** 输入:** s = "abcdef"
110
- >
93
+ >
111
94
> ** 输出:** -1
112
- >
95
+ >
113
96
> ** 解释:** 不存在出现至少三次的特殊子字符串。因此返回 -1 。
114
- >
115
- >
116
97
117
98
** 示例 3:**
118
99
119
- >
120
- >
121
- >
122
- >
123
- >
124
100
> ** 输入:** s = "abcaba"
125
- >
101
+ >
126
102
> ** 输出:** 1
127
- >
103
+ >
128
104
> ** 解释:** 出现三次的最长特殊子字符串是 "a" :子字符串 "_ ** a** _ bcaba"、"abc _ ** a** _ ba" 和 "abcab _ ** a** _ "。
129
- >
105
+ >
130
106
> 可以证明最大长度是 1 。
131
- >
132
- >
133
107
108
+ ** 提示:**
134
109
110
+ - ` 3 <= s.length <= 50 `
111
+ - ` s ` 仅由小写英文字母组成。
135
112
136
- ** 提示: **
113
+ ## 解题思路
137
114
138
- * ` 3 <= s.length <= 50 `
139
- * ` s ` 仅由小写英文字母组成。
115
+ ### 思路一:暴力遍历
140
116
117
+ 1 . ** 遍历子串长度:**
118
+ - 从最长可能的长度开始遍历,逐步检查是否存在符合条件的特殊子串。
119
+ 2 . ** 检查特殊字符串:**
141
120
142
- ## 解题思路
121
+ - 使用 ` new Set(sub).size ` 检查子串是否由单一字符组成。
122
+ - 如果 ` size === 1 ` ,表示子串为特殊字符串。
123
+
124
+ 3 . ** 统计子串出现次数:**
125
+
126
+ - 利用 ` Map ` 数据结构记录每个特殊子串的出现次数。
127
+ - 遍历完成后,检查是否有子串的出现次数达到 3 次以上。
128
+
129
+ 4 . ** 返回结果:**
130
+ - 如果找到满足条件的子串,返回其长度。
131
+ - 如果没有找到,返回 ` -1 ` 。
143
132
144
133
#### 复杂度分析
145
134
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
+ - 其他变量占用常量空间。
148
213
149
214
## 代码
150
215
216
+ ::: code-tabs
217
+
218
+ @tab 暴力遍历
219
+
151
220
``` 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 双指针
152
255
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
+ };
153
345
```
154
346
347
+ :::
348
+
155
349
## 相关题目
156
350
157
351
<!-- prettier-ignore -->
158
352
| 题号 | 标题 | 题解 | 标签 | 难度 | 力扣 |
159
353
| :------: | :------ | :------: | :------ | :------: | :------: |
160
354
| 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