介紹#
字串題大多不需要特別的演算法,難的是細節:index 的邊界、操作的先後順序、Python 字串不可變。幾個固定的寫法:
| 形式 | 做法 | 例子 |
|---|---|---|
| 模擬 | 用 list 當 stack 組結果,最後 join | 特殊字元操作、合併相近字元 |
| 解析 | 每個位置嘗試比對所有 pattern,比對到就跳過 | HTML entity |
長度 k 的子字串 | 枚舉起點,丟進集合 | 所有二進位碼、三個相同數字 |
| 字元分類 | 母音 / 子音 / 數字分開計數 | 最多的母音和子音、母音排序 |
| 固定起點往右擴 | 增量維護計數 | 每種字元次數相同 |
| 切成單字 | split() 後逐字處理 | 最後一個單字、Goat Latin、最常見的單字 |
字串上的 DP(LCS、編輯距離)見 String DP。
模板一:用 list 模擬#
Python 字串不可變,s += c 每次都會複製。用 list 當結果,最後再 "".join:
res = []
for ch in s:
if ch == "*":
if res: # 判斷寫在裡面,不要寫成 ch == "*" and res
res.pop()
elif ch == "#":
res += res
elif ch == "%":
res.reverse()
else:
res.append(ch)
return "".join(res)
- 特殊字元操作(LC 3612):上面的程式。
- 合併相近的相同字元(LC 4019):保留下來的字不會再變,所以只要記每種字元在結果裡最後的位置,新字元跟它的距離
<= k就丟掉。 - 最大的偶數(LC 3798):把結尾的 1 一直刪掉。
模板二:在每個位置嘗試比對#
i, out = 0, []
while i < len(text):
for pattern, rep in table.items():
if text.startswith(pattern, i): # 不會超出範圍
out.append(rep)
i += len(pattern)
break
else:
out.append(text[i])
i += 1
- HTML entity(LC 1410):不能用一連串的
replace:先換出來的&會跟後面的字組成新的 entity。 s + s包含所有旋轉(LC 796、459):goal是s的旋轉 ⇔ 長度相同且goal in s + s;s有重複的週期 ⇔s in (s + s)[1:-1]。
模板三:長度 k 的所有子字串#
seen = {s[i:i + k] for i in range(len(s) - k + 1)} # 最後一個起點是 len(s) - k
- 所有長度
k的二進位碼(LC 1461):集合大小要是2^k;子字串數量len(s) - k + 1不到2^k就一定不夠。 - 三個相同的數字(LC 2264):只有 10 種候選,從
"999"往下檢查in num。
模板四:字元分類計數#
vowels = set("aeiou")
cnt = Counter(s)
best_vowel = max((cnt[c] for c in vowels), default=0)
best_consonant = max((v for c, v in cnt.items() if c not in vowels), default=0)
- 最多的母音加最多的子音(LC 3541):上面的程式。
- 母音比子音的分數(LC 3813):只算字母,空白和數字都不算。
- 依次數排序母音(LC 3913):只把母音抽出來,用
(-次數, 第一次出現的位置)排序後放回原本母音的位置。 - 前綴的不同字元數(LC 3803):邊掃邊把字元加進集合。
- 相鄰數字的差(LC 3931):直接比較相鄰兩個。
模板五:固定起點往右擴#
for i in range(n):
cnt = Counter()
max_freq = 0
for j in range(i, n):
cnt[s[j]] += 1
max_freq = max(max_freq, cnt[s[j]])
if max_freq * len(cnt) == j - i + 1: # 每種字元都出現 max_freq 次
best = max(best, j - i + 1)
- 每種字元次數都相同(LC 3713):「每種都一樣多」等於「最多的次數 × 種類數 = 長度」,每一步
O(1)判斷。
模板六:切成單字再處理#
words = s.split() # 不帶參數:連續空白當一個分隔,頭尾空白丟掉
res = []
for i, w in enumerate(words):
res.append(轉換(w, i))
return " ".join(res)
- 數單字、最後一個單字(LC 434、58):
len(s.split())、s.split()[-1]。split(" ")遇到連續空白或結尾空白會產生空字串。 - 逐字轉換(LC 824、557):每個單字各自處理,需要位置時用
enumerate。 - 連續幾個單字(LC 1078):檢查
words[i]、words[i + 1]時,迴圈只跑到len(words) - 2。 - 正規化再切(LC 819、929):先把標點換成空白再切;信箱則用
@切成兩半,只處理前半段,接回去時保留@。
主要參數說明#
s[i:j]不包含j;長度k的子字串最後一個起點是len(s) - k。str.startswith(p, i)從i開始比對,超出範圍時回傳False,不會報錯。
複雜度#
模擬和計數是 O(n);收集所有長度 k 的子字串是 O(n · k);固定起點往右擴是 O(n²)。
常見錯誤#
這些都是我練習時實際寫錯過的:
- 連續
replace:"&gt;"先換成">",再被換成">"(LC 1410)。 - 往後看沒檢查邊界:
&在字串最後時text[right]超出範圍(LC 1410)。 - 條件寫成
ch == "*" and res:結果是空的時候條件不成立,*掉到最後的else被加進結果(LC 3612)。 - 子字串的範圍差一:寫成
s[right - k: right],最後一個視窗沒收到(LC 1461)。 - 用原本的 index 換算目前的位置:刪除的數量算錯,距離就錯了;直接記結果裡的位置比較簡單(LC 4019)。
- 多了一層不必要的迴圈:外面再枚舉 26 種「種類數」,TLE(LC 3713)。
split(" ")拿最後一個單字:結尾有空白時會拿到空字串(LC 58)。- 刪掉標點而不是換成空白:
a'a變成aa(LC 819)。 - 接回字串時把分隔符號弄丟:
"a@bx.com"和"ab@x.com"接成同一個字串(LC 929)。 - 只檢查
i + 1的邊界卻存取i + 2(LC 1078)。 - 數字被當成子音:子音必須是字母(LC 3136)。
- 對單一字元做切片:
for c in s的c[:3]永遠只有一個字元(LC 551)。 - 從後往前每
k個分組時計數器差一,或多加一個分隔符號(LC 482)。 - 迴圈正常跑完時沒有回傳:最短字串整個都是共同前綴時回傳
None(LC 14)。 goal in s + s沒有檢查長度相等:"aa"也在"a" + "a"裡(LC 796)。- tuple 跟 list 比較:
zip產生 tuple,sorted回傳 list,==永遠是False(LC 944)。 - 字典序只看第一個不同的字元,而且前綴比較短的要在前面:
["ll", "l"]沒有排好(LC 953)。 - 連續段落重設成 0:新的字元本身就是長度 1(LC 1446)。
- 把字元跟整數比較:
'1' == 1永遠是False(LC 1869)。 - 剛好填滿也換行:寬度剛好 100 時還在這一行(LC 806)。
- 相同的字串也要交換一次:沒有重複字元就做不到(LC 859)。
相關 LeetCode#
LC 1410HTML Entity ParserLC 3803Count Residue PrefixesLC 4019Merge Close Characters IILC 1461Check If a String Contains All Binary Codes of Size KLC 3541Find Most Frequent Vowel and ConsonantLC 3713Longest Balanced Substring ILC 2264Largest 3-Same-Digit Number in StringLC 3798Largest Even NumberLC 3931Check Adjacent Digit DifferencesLC 3612Process String with Special Operations ILC 3813Vowel-Consonant ScoreLC 3913Sort Vowels by FrequencyLC 28Find the Index of the First Occurrence in a StringLC 187Repeated DNA SequencesLC 696Count Binary SubstringsLC 821Shortest Distance to a CharacterLC 819Most Common WordLC 929Unique Email AddressesLC 804Unique Morse Code WordsLC 434Number of Segments in a StringLC 599Minimum Index Sum of Two ListsLC 482License Key FormattingLC 520Detect CapitalLC 824Goat LatinLC 1078Occurrences After BigramLC 58Length of Last WordLC 1108Defanging an IP AddressLC 500Keyboard RowLC 709To Lower CaseLC 3136Valid WordLC 551Student Attendance Record ILC 14Longest Common PrefixLC 796Rotate StringLC 859Buddy StringsLC 944Delete Columns to Make SortedLC 953Verifying an Alien DictionaryLC 1446Consecutive CharactersLC 1869Longer Contiguous Segments of Ones than ZerosLC 459Repeated Substring PatternLC 521Longest Uncommon Subsequence ILC 657Robot Return to OriginLC 806Number of Lines To Write String
