↓快轉到主要內容
  1. Algorithms/

String 字串處理

·4 分鐘· ·
演算法 String Simulation Hash-Table
Wei Yi Chung
作者
Wei Yi Chung
Working at the contributing of open source, distributed systems, and data engineering.
目錄

介紹
#

字串題大多不需要特別的演算法,難的是細節: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:"&amp;gt;" 先換成 "&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 1410 HTML Entity Parser
  • LC 3803 Count Residue Prefixes
  • LC 4019 Merge Close Characters II
  • LC 1461 Check If a String Contains All Binary Codes of Size K
  • LC 3541 Find Most Frequent Vowel and Consonant
  • LC 3713 Longest Balanced Substring I
  • LC 2264 Largest 3-Same-Digit Number in String
  • LC 3798 Largest Even Number
  • LC 3931 Check Adjacent Digit Differences
  • LC 3612 Process String with Special Operations I
  • LC 3813 Vowel-Consonant Score
  • LC 3913 Sort Vowels by Frequency
  • LC 28 Find the Index of the First Occurrence in a String
  • LC 187 Repeated DNA Sequences
  • LC 696 Count Binary Substrings
  • LC 821 Shortest Distance to a Character
  • LC 819 Most Common Word
  • LC 929 Unique Email Addresses
  • LC 804 Unique Morse Code Words
  • LC 434 Number of Segments in a String
  • LC 599 Minimum Index Sum of Two Lists
  • LC 482 License Key Formatting
  • LC 520 Detect Capital
  • LC 824 Goat Latin
  • LC 1078 Occurrences After Bigram
  • LC 58 Length of Last Word
  • LC 1108 Defanging an IP Address
  • LC 500 Keyboard Row
  • LC 709 To Lower Case
  • LC 3136 Valid Word
  • LC 551 Student Attendance Record I
  • LC 14 Longest Common Prefix
  • LC 796 Rotate String
  • LC 859 Buddy Strings
  • LC 944 Delete Columns to Make Sorted
  • LC 953 Verifying an Alien Dictionary
  • LC 1446 Consecutive Characters
  • LC 1869 Longer Contiguous Segments of Ones than Zeros
  • LC 459 Repeated Substring Pattern
  • LC 521 Longest Uncommon Subsequence I
  • LC 657 Robot Return to Origin
  • LC 806 Number of Lines To Write String

相關文章

Array 陣列掃描
·3 分鐘
演算法 Array Simulation Enumeration
相鄰比較與走過單調段(上坡、下坡、山峰與山谷)、一次掃描維護前綴 / 後綴的最大值與總和、小範圍枚舉配對,以及用 tuple 當 key 處理平手
Hash Table 雜湊表
·4 分鐘
演算法 Hash-Table Counting Array
計數與「次數的次數」、把陣列本身當雜湊表(負號標記)、多個 key 同時計數(列、欄、對角線)、固定左端點增量計數,以及用正規化後的 key 分組
Matrix 矩陣
·3 分鐘
演算法 Matrix Simulation Array
轉置與旋轉、二維 index 和一維序列互換、鄰居與對角線、四個邊界的螺旋走訪
Stack 與單調棧
·3 分鐘
演算法 Stack Monotonic-Stack Greedy Simulation
巢狀結構解析、跟前一個元素合併的模擬、單調棧、貪心刪字元取字典序最小,以及 lazy deletion 的 Max Stack
Trie 字典樹
·2 分鐘
演算法 Trie String Prefix Bit-Manipulation
用巢狀 dict 建 Trie、在節點上記數量、搭配 DFS 搜尋,以及什麼時候其實不需要 Trie
Two Pointers 雙指針
·5 分鐘
演算法 Two-Pointers String Sorting
對撞指針、兩個序列各一個指針、跳過空白比對位置、排序後數 pair,以及原地覆寫的寫入指針