3090. Maximum Length Substring With Two Occurrences
題目 / Problem
中文: 給定一個字串 s,請回傳一個子字串的最大長度,這個子字串中每個字元最多出現兩次。
English: Given a string s, return the maximum length of a substring such that each character appears at most twice inside it.
約束 / Constraints:
- 2 <= s.length <= 100
- s 只由小寫英文字母組成 / s consists only of lowercase English letters.
範例 / Example:
- Input: s = "bcbbbcba"
- Output: 4
- 說明 / Explanation: 子字串 "bcba"(原字串最後四個字元)長度為 4,其中 b 出現 2 次、c 出現 1 次、a 出現 1 次,都不超過兩次。沒有更長的合法子字串了。/ The substring "bcba" (the last four characters) has length 4; b appears twice, c and a once each — none exceeds twice. No longer valid substring exists.
名詞解釋 / Glossary
- 子字串 / Substring: 字串中連續的一段字元。例如
"bcbbbcba"的"bcb"是子字串,但"bcb...a"中跳著取的字元不算。/ A contiguous run of characters inside the string. Contiguity is what makes this different from a subsequence. - 滑動視窗 / Sliding window: 用兩個指標
left和right框出一段連續區間,隨著掃描不斷向右移動、伸縮這個區間的技巧。/ A technique using two indicesleftandrightto bound a contiguous range that grows and shrinks as you scan, so you never re-examine the whole string from scratch. - 雙指針 / Two pointers: 兩個一起移動的索引變數;這裡就是視窗的左右邊界。/ Two index variables that move together — here they are the window's boundaries.
- 計數陣列 / Count array: 一個大小固定的陣列,用索引代表字元、值代表出現次數。因為只有 26 個小寫字母,用
cnt[26]就夠了。/ A fixed-size array where the index represents a character and the value stores how many times it currently appears. Since there are only 26 lowercase letters,cnt[26]suffices — it acts like a tiny hash table. - 不變量 / Invariant: 在演算法每一步都保持為真的條件。這裡的不變量是「視窗內每個字元出現次數 ≤ 2」。/ A condition that stays true at every step of the algorithm. Here: "every character inside the window appears at most twice."
思路
最直接的想法是暴力法:枚舉所有可能的子字串(選一個起點、一個終點),對每個子字串數一遍每種字元的出現次數,若全部都 ≤ 2 就更新答案。因為 s.length 最多只有 100,這種 O(n³) 或 O(n²·26) 的做法其實完全能過。但它做了很多重複工作——每次都從頭數起。我們可以更聰明。
更好的做法是滑動視窗。我們維持一個區間 [left, right],並保持一個不變量:視窗裡每個字元最多出現兩次。用一個 cnt[26] 陣列記錄視窗內每個字母的出現次數。我們讓 right 從左到右一格一格擴張,每次把 s[right] 加入視窗(對應的計數加一)。加入之後,如果這個字元的計數變成 3,就代表不變量被破壞了——這時我們從左邊縮小視窗:不斷把 s[left] 移出(計數減一)並讓 left 右移,直到那個超標的字元計數降回 2 為止。每當視窗合法時,用當前視窗長度 right - left + 1 更新答案。因為每個字元最多被 right 加入一次、被 left 移出一次,總共只掃兩遍,所以是 O(n)。關鍵在於:一旦某字元超過兩次,唯一能修復的方法就是丟掉左邊那個多出來的同字元,而 left 只會往右走、不會倒退,這保證了效率也保證了正確性。
The most direct idea is brute force: enumerate every substring (pick a start and an end), count each character's occurrences, and if all are ≤ 2, update the answer. Since s.length is at most 100, this O(n³) approach easily passes. But it repeats a lot of work by recounting from scratch every time. We can do better.
The cleaner approach is a sliding window. We keep a range [left, right] and maintain the invariant that every character inside appears at most twice, tracking counts in a cnt[26] array. We expand right one step at a time, adding s[right] to the window (incrementing its count). If that count hits 3, the invariant is broken, so we shrink from the left: repeatedly remove s[left] (decrement) and advance left until the offending character drops back to 2. Whenever the window is valid, we update the answer with the current length right - left + 1. Each character is added once by right and removed at most once by left, so the whole thing is two passes — O(n). The key insight: the only way to fix an over-count is to discard the extra copy on the left, and since left never moves backward, both correctness and efficiency are guaranteed.
逐步走查 / Walkthrough
輸入 / Input: s = "bcbbbcba"(索引 0..7)。我們追蹤 left、right、計數陣列,以及答案 ans。
| right | s[right] | 加入後動作 / action | 縮左?/ shrink? | left | 視窗 / window | 長度 / len | ans |
|---|---|---|---|---|---|---|---|
| 0 | b | cnt[b]=1 | 否 / no | 0 | b |
1 | 1 |
| 1 | c | cnt[c]=1 | 否 / no | 0 | bc |
2 | 2 |
| 2 | b | cnt[b]=2 | 否 / no | 0 | bcb |
3 | 3 |
| 3 | b | cnt[b]=3 ✗ | 是:移出 s[0]=b,cnt[b]=2,left→1 / yes | 1 | cbb |
3 | 3 |
| 4 | b | cnt[b]=3 ✗ | 是:移出 s[1]=c,cnt[c]=0,left→2;再移出 s[2]=b,cnt[b]=2,left→3 / yes | 3 | bb |
2 | 3 |
| 5 | c | cnt[c]=1 | 否 / no | 3 | bbc |
3 | 3 |
| 6 | b | cnt[b]=3 ✗ | 是:移出 s[3]=b,cnt[b]=2,left→4 / yes | 4 | bcb |
3 | 3 |
| 7 | a | cnt[a]=1 | 否 / no | 4 | bcba |
4 | 4 |
最終答案 / Final answer: 4(子字串 "bcba")。
Solution — C
// 演算法:滑動視窗 + 26 大小計數陣列。right 擴張,若某字元計數超過 2 就從左縮小,
// 每個合法視窗更新最大長度。時間 O(n),空間 O(1)。
// Algorithm: sliding window with a 26-slot count array. Expand right; if any char
// exceeds 2, shrink from left; update the max valid window length. O(n) time, O(1) space.
int maximumLengthSubstring(char* s) {
int cnt[26] = {0}; // 每個字母在視窗內的出現次數,初值全 0 / occurrences of each letter in the window, all zero
int left = 0; // 視窗左邊界 / left boundary of the window
int ans = 0; // 目前找到的最大合法長度 / best valid length so far
// right 逐一掃過整個字串,作為視窗右邊界 / right scans the whole string as the window's right edge
for (int right = 0; s[right] != '