給定敏感詞 w、文本 p (長度 ≤ 5×10⁶),**反覆**在 p 中尋找 w 第一次出現並刪除,直到不再出現。輸出最終文本。
範例:w=abc, p=aaabcbc → 刪除 abc 得 aabc → 再刪 abc 得 a。
| 演算法 | 預處理 | 匹配 | 適用場景 | 實現難度 |
|---|---|---|---|---|
暴力/標準庫 find/erase | O(1) | O(N×M) | 極小數據、原型驗證 | 極低 |
| KMP | O(M) | O(N) | 單模式、線性保證 | 低 |
| Boyer-Moore (BMH) | O(M+Σ) | O(N) 平均更快 | 單模式、長模式、字母表大 | 中 |
| AC 自動機 | O(總模式長) | O(N + 輸出) | **多模式**同時匹配 | 高 |
| Z-Algorithm / KMP 變體 | O(M) | O(N) | 單模式、代碼簡潔 | 低 |
刪除 w 可能導致前後字符拼接形成新的 w (如 aabc 刪中間 abc 後剩 a,但 aaabcbc 刪第一個 abc 後 a+bc 不形成新詞)。**單次 KMP/BM 不夠**,需模擬棧或雙指針原地處理。
string censor(string w, string p) {
int n = p.size(), m = w.size();
if (m == 0) return p;
// KMP next 陣列 (0-based)
vector nxt(m);
for (int i = 1, j = 0; i < m; i++) {
while (j > 0 && w[i] != w[j]) j = nxt[j-1];
if (w[i] == w[j]) j++;
nxt[i] = j;
}
string res; res.reserve(n);
vector matchLen; matchLen.reserve(n); // 對應 res 每位的匹配長度
for (char c : p) {
res.push_back(c);
int j = matchLen.empty() ? 0 : matchLen.back();
while (j > 0 && c != w[j]) j = nxt[j-1];
if (c == w[j]) j++;
matchLen.push_back(j);
if (j == m) { // 找到敏感詞
res.resize(res.size() - m);
matchLen.resize(matchLen.size() - m);
}
}
return res;
}
push_back 進字符,匹配成功 resize 彈出matchLen 可用 vector (m ≤ 5e6 < 65535? 否則用 int)必須用 **AC 自動機 (Aho-Corasick)** + 棧模擬。構建 Trie + 失敗指針,掃描時同步維護狀態棧,刪除時回退到失敗指針對應狀態。
| w | p | 輸出 |
|---|---|---|
| abc | aaabcbc | a |
| b | bbb | (空) |
| abc | ab | ab |
| aa | aaaa | (空) |
| aba | ababa | ba |
ACM 敏感詞刪除題核心是**單次線性掃描 + 棧模擬刪除 + KMP 狀態同步**。暴力 find/erase 會 TLE。KMP/BM 單模式足夠,多模式上 AC 自動機。代碼關鍵:res 與 matchLen 雙棧同步,刪除時同步回退。