Leetcode-3090-每个字符最多出现两次的最长子字符串
题目
给你一个字符串 s,请返回满足以下条件的最长子字符串的长度:
- 每个字符最多出现两次。
示例 1:
1 | 输入:s = "bcbbbcba" |
示例 2:
1 | 输入:s = "aaaa" |
提示:
2 <= s.length <= 100s仅包含小写英文字母。
思路
滑动窗口
本题是 3. 无重复字符的最长子串 的变体,只需把「每个字符最多出现 1 次」放宽为「最多出现 2 次」,套用同一个不定长滑动窗口模板:
right不断右移,将新字符加入窗口,频次 +1;- 若该字符频次超过 2,说明窗口不合法,从
left开始收缩:移除left处字符,频次 -1,left++,直到该字符频次 ≤ 2; - 此时窗口
[left, right]一定合法,用right - left + 1更新答案。
因为 left、right 都只向右移动,每个字符至多被加入、移除各一次,时间复杂度 O(n);频次表最多存 26 个小写字母,空间复杂度 O(26)。
示例推演
以 s = "bdbbabccad" 为例:
| right | 字符 | 收缩后窗口 | 窗口内频次 | ans |
|---|---|---|---|---|
| 0 | b | [0,0]=”b” |
b:1 | 1 |
| 1 | d | [0,1]=”bd” |
b:1, d:1 | 2 |
| 2 | b | [0,2]=”bdb” |
b:2, d:1 | 3 |
| 3 | b | [1,3]=”dbb”(b 超 2,移除 left=0 的 b) |
b:2, d:1 | 3 |
| 4 | a | [1,4]=”dbba” |
b:2, d:1, a:1 | 4 |
| 5 | b | [3,5]=”bab”(b 超 2,依次移除 d、b) |
b:2, a:1 | 4 |
| 6 | c | [3,6]=”babc” |
b:2, a:1, c:1 | 4 |
| 7 | c | [3,7]=”babcc” |
b:2, a:1, c:2 | 5 |
| 8 | a | [3,8]=”babcca” |
b:2, a:2, c:2 | 6 |
| 9 | d | [3,9]=”babccad” |
b:2, a:2, c:2, d:1 | 7 |
返回 7。
出错信息(备注)
写代码时踩了一个坑,记录在此:收缩窗口的 while 循环里错误地写了
1 | ch = s.charAt(left); |
复用了外层变量 ch。问题在于:ch 是 right 处字符——正是它触发频次超限,while 条件 occ.get(ch) > 2 要靠它判断是否继续收缩。在循环内把它改成 left 处字符后,条件判断的对象变成了刚被移除的那个字符,而它的频次刚刚减 1,大概率已经 ≤ 2,于是循环提前退出,超限字符的频次根本没降下来,窗口仍然不合法。
反例:s = "abbbc",正确答案是 3(如 “abb”、”bbc”)。
- right=3 时窗口 “abbb”,b 出现 3 次,进入收缩循环;
- 错误写法把
ch改为s.charAt(0) = 'a',移除 a 后occ['a'] = 0,条件0 > 2不成立,循环立刻退出; - 此时窗口 “bbb” 中 b 仍出现 3 次,却按合法窗口参与计算,最终返回 4。
正确做法:用一个新变量接收 left 处字符,循环条件里的 ch 保持不动:
1 | while (occ.get(ch) > 2) { |
本质:while 条件的判断对象(right 处超限字符)和循环体的操作对象(left 处被移除字符)不是同一个字符,必须用两个变量区分。
代码
1 | class Solution { |
复杂度:时间 O(n),空间 O(26)(HashMap 最多存 26 个键,也可用 int[26] 数组进一步精简)。
关键点
- 模板识别:「每个字符最多出现 k 次」是经典滑动窗口模型,本题 k=2。窗口内频次超过 k 就收缩左边界,收缩后的窗口必然合法,此时更新答案。
- 收缩条件:
while只需判断当前加入的字符(right处)是否超限——收缩过程中其他字符只会变少,不会新产生超限,因此不必检查整个频次表。 - 变量隔离:循环条件的判断对象与循环体的操作对象是不同字符,不要复用同一个变量(见「出错信息」一节的反例)。
- 复杂度直觉:左右指针均单调右移,均摊
O(n)。
相关题目
- 3. 无重复字符的最长子字符串 — 同模板,k=1 的特例。
- 2958. 最多 K 个重复元素的最长子数组 — 本题的泛化版本。
- 1695. 删除子数组的最大得分 — 滑动窗口 + 窗口内元素去重计数。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 林间笔记!