演算法筆記-String
String
用於儲存字串的資料類型,內部為一個個的字元構成,可以進行很多類似陣列的操作,有以下幾個常見的操作:
- size 回傳字串長度
- [] 存取對應下標的字元
- find 回傳指定字元的位置
- substr 取出指定的子字串
- reverse 反轉指定的字串
Stringstream
經常在操作字串時會使用到的類別,常用於類型轉換
或是分割字串
。
型態轉換
使用 stringstream
1 | //int -> str |
使用函數
1 | //int -> str |
分割字串
空白分割
1 | string str = "Welcome to my blog", tmp; |
其他分割符
1 | string str = "discuss.leetcode.com", tmp; |
字串匹配問題
給定一個主串 T
以及子串 p
,在主串中尋找子串是否存在,以及其所在的位置。
樸素演算法
最容易想到的演算法,不需要進行預處理,從 T[0]
以及 p[0]
開始比較,如果相同就接著往下比較,如果出現相異的情況就退回到 T[1]
以及 p[0]
比較,直到主串的結尾。
1 | int bruteForce(string T, string p) { |
這種演算法因為每次都要退回,效率較差,因此比較不推薦使用。
KMP 演算法
透過提前建立的 部分匹配表
可以在匹配失敗時利用失敗的資訊將子串的下標進行移動,進而繼續匹配,避免了之前的重複情形。
部分匹配表
通常會以 next
作為命名,其中每個位置 next[j]
的意義是子串 [0,j] 之間的最長相同前後綴
,同時也是在之後 p[j + 1]
發生錯誤匹配時可以跳過檢查的長度。
範例:
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
p[j] | A | B | C | D | A | B | D |
next[j] | 0 | 0 | 0 | 0 | 1 | 2 | 0 |
- i = 4 時,“ABCDA” 有相同前後綴 “A”,長度為 1。
- i = 5 時,“ABCDAB” 有相同前後綴 “AB”,長度為 2。
1 | vector<int> buildNext(string p) { |
匹配原則
和樸素演算法一樣,從 T[0]
以及 p[0]
開始比較,如果相同就接著往下比較,不同的是在發生錯誤匹配時的處理,此時不用回退 i
的值,而是將 j
移到 next[j - 1]
對應的位置。因為我們知道相同前後綴
的長度,因此就可以不用檢查對應的前綴長度,在不用回退的情況下執行至主串的結尾。
1 | int KMP(string T, string p) { |
BM 演算法
利用兩種規則來跳過無法匹配的情況,節省比較的時間,移動的方向是由左往右,但是匹配時的方向是從後方開始匹配。
- 壞字符規則
- 壞字符:匹配失敗時主字串的字符。
- 好後綴規則
- 好後綴:已經匹配成功的字串 (由後往前匹配,所以是後綴)。
壞字符規則
- 匹配失敗時,將子串向右移動,使得
主串的壞字符
與子串中最右方的壞字符
對齊,移動位數 =子串的錯誤匹配位置 - 壞字符在子串中的最右位置
。

- 如果壞字符在子串中不存在,設定出現位置為
-1
,即將整個子串右移 1 位。

壞字符位置表
(紀錄每個字符出現的最右位置):
1 | unordered_map<int, int> buildBadCharTable(string p){ |
好後綴規則
- 找到子串中前一個
好後綴
出現的位置,將其與主串中的好後綴
對齊,右移的位數 =好後綴的位置 - 好後綴在子串中的上一個位置
。

- 尋找子串中最長的相同前後綴 (子串的前綴 = 好後綴的後綴),將兩者對齊,右移的位數 =
好後綴的位置 - 最長前綴的位置
。

- 如果以上兩個條件都不存在,可以直接將整個子串右移,右移的位數 =
子串的長度
。

定義一個後綴數組 suffix
儲存最大相同後綴的長度,每個值 suffix[i]
的意義是以下標 i 為結尾與子串匹配的最大相同後綴長
。
1 | vector<int> buildSuffix(string p){ |
利用 suffix
數組,可以提前建立好後綴規則位移表
,每個值 goodSuffixTable[j]
代表在下標 j
處遇到錯誤匹配時,根據好後綴規則所要右移的距離。
- Type3

- Type2

- Type1

1 | vector<int> buildGoodSuffixTable(string p){ |
如果同時有兩種情況成立,會選擇移動距離較小者,因此取值的順序是由 Type3
、Type2
到 Type1
。
合併使用
由主串的開頭開始,逐步將子串右移,每次從子串的後方開始比對,比對失敗時比較兩種規則產生的右移距離,取距離較大者進行移動,直到成功匹配為止。
1 | int BM(string T, string p){ |
內建 find 函數
1 | size_t find (const char* s, size_t pos, size_t n) const; |
<string>
內的 find
函數也可以在字串裡面尋找子串是否出現,如果有則會返回出現的位置,否則返回 string::npos
,pos
為尋找的主串起始位置,n
為子串的匹配字元數 (前 n 位)。
1 | size_t found = T.find(p); |
練習
LeetCode 5. Longest Palindromic Substring
Given a string s
, return the longest palindromic substring in s
.
Example 1:
Input: s = “babad”
Output: “bab”
Example 2:
Input: s = “cbbd”
Output: “bb”
Solution:
1 | class Solution { |
建立一個二維的陣列 v
儲存字串的每個子串是否是對稱的,當子串的開頭與結尾相同時:
- 長度 <= 3:此子串必定對稱。
- 長度 > 3:檢查
去除頭尾的子串
是否對稱。
之後記錄下最長的對稱子串。
LeetCode 28. Find the Index of the First Occurrence in a String
Given two strings needle
and haystack
, return the index of the first occurrence of needle
in haystack
, or -1
if needle
is not part of haystack
.
Example 1:
Input: haystack = “sadbutsad”, needle = “sad”
Output: 0
Example 2:
Input: haystack = “leetcode”, needle = “leeto”
Output: -1
Solution:
使用前面的樸素演算法、KMP 演算法以及 BM 演算法都能通過,不過需要注意主串比子串短時有的演算法可能會有錯誤。
LeetCode 459. Repeated Substring Pattern
Given a string s
, check if it can be constructed by taking a substring of it and appending multiple copies of the substring together.
Example 1:
Input: s = “abab”
Output: true
Example 2:
Input: s = “aba”
Output: false
Example 3:
Input: s = “abcabcabcabc”
Output: true
Solution:
1 | class Solution { |
建立 KMP 算法中的 next
陣列,如果字串是由重複的子串構成,字串長度 - 最大相同前後綴長
會是最小的重複子串長度,檢查字串長度是否為子串的整數倍即可。