本文内容参考《算法 》,《算法导论》,OI Wiki 。
字符串匹配
例题
暴力
时间复杂度:最坏 \(O(NM)\),平均 \(O(N)\)。
空间复杂度:\(O(1)\)。
1 2 3 4 5 6 7 8 9 10 11 12 13 private static int bruteForce (String text, String pattern) { int i, j; int n = text.length(), m = pattern.length(); for (i = 0 , j = 0 ; i < n && j < m; i++) { if (text.charAt(i) == pattern.charAt(j)) { j++; } else { i -= j; j = 0 ; } } return j == m ? i - m : -1 ; }
KMP
时间复杂度:\(O(N)\)。
空间复杂度:\(O(M)\)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 private static int kmp (String text, String pattern) { int n = text.length(), m = pattern.length(); if (m > n) return -1 ; int [] next = new int [m]; for (int i = 1 , j = 0 ; i < m; i++) { while (j > 0 && pattern.charAt(i) != pattern.charAt(j)) { j = next[j - 1 ]; } if (pattern.charAt(i) == pattern.charAt(j)) { j++; } next[i] = j; } for (int i = 0 , j = 0 ; i < n; i++) { while (j > 0 && text.charAt(i) != pattern.charAt(j)) { j = next[j - 1 ]; } if (text.charAt(i) == pattern.charAt(j)) { j++; } if (j == m) { return i - m + 1 ; } } return -1 ; }
Boyer-Moore
时间复杂度:最坏 \(O(NM)\),平均 \(O(\frac{N}{M})\)。
空间复杂度:\(O®\)。
1 System.out.println("TODO" );
Rabin-Karp
时间复杂度:\(O(N)\)。
空间复杂度:\(O(1)\)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 private static final int P = 13331 ;private static int rabinKarp (String text, String pattern) { int n = text.length(), m = pattern.length(); if (m > n) return -1 ; long PM = pow(P, m - 1 ); long patHash = hash(pattern, m); long txtHash = hash(text, m); if (txtHash == patHash) { return 0 ; } for (int i = m; i < n; i++) { txtHash = txtHash - text.charAt(i - m) * PM; txtHash = txtHash * P + text.charAt(i); if (txtHash == patHash) { return i - m + 1 ; } } return -1 ; } private static long pow (int a, int n) { long res = 1L , x = a; for (; n != 0 ; x *= x, n >>= 1 ) { if ((n & 1 ) == 1 ) { res *= x; } } return res; } private static long hash (String s, int m) { long h = 0L ; for (int i = 0 ; i < m; i++) { h = h * P + s.charAt(i); } return h; }
字符串哈希
例题
实现
时间复杂度:预处理 \(O(N)\),获取哈希值 \(O(1)\)。
空间复杂度:\(O(N)\)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class StringHash { private final int K = 2 ; private static final long [] b = {131 , 13331 }; private static final long [] m = {1_000_000_007 , 998244353 }; private int index; private final long [][] h, p; public StringHash (int n) { h = new long [K][n + 1 ]; p = new long [K][n + 1 ]; for (int i = 0 ; i < K; i++) { p[i][0 ] = 1 ; } } public StringHash (char [] s) { this (s.length); for (char c : s) add(c); } public void add (char c) { for (int j = 0 ; j < K; j++) { p[j][index + 1 ] = p[j][index] * b[j] % m[j]; h[j][index + 1 ] = (h[j][index] * b[j] + c) % m[j]; } index++; } public long [] get(int l, int r) { long [] res = new long [K]; for (int i = 0 ; i < K; i++) { long t = h[i][r + 1 ] - h[i][l] * p[i][r - l + 1 ]; res[i] = (t % m[i] + m[i]) % m[i]; } return res; } public int length () { return index; } }
字典树(Trie)
例题
实现
时间复杂度:插入和查找都是 \(O(k)\),其中 \(k\) 为字符串的长度。
空间复杂度:\(O(nR)\),其中 \(n\) 为节点总数,\(R\) 为字母表大小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class Trie { private static final int R = 26 ; private final Node root; private static class Node { boolean exist; Node[] next = new Node [R]; } public Trie () { root = new Node (); } public void insert (String word) { Node node = root; for (int i = 0 ; i < word.length(); i++) { int idx = word.charAt(i) - 'a' ; if (node.next[idx] == null ) { node.next[idx] = new Node (); } node = node.next[idx]; } node.exist = true ; } public boolean search (String word) { Node node = root; for (int i = 0 ; i < word.length(); i++) { int idx = word.charAt(i) - 'a' ; if (node.next[idx] == null ) { return false ; } node = node.next[idx]; } return node.exist; } }
AC 自动机
例题
实现
时间复杂度:插入 \(O(k)\),构建 \(O(nR)\),查询 \(O(k+n)\),其中 \(k\) 为字符串的长度。
空间复杂度:\(O(nR)\),其中 \(n\) 为节点总数,\(R\) 为字母表大小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 class AhoCorasickAutomaton { private static final int R = 26 ; private final Node root; private final List<Node> patternNodes; private final List<Node> levelOrderNodes; private static class Node { int cnt; Node fail; Node[] next = new Node [R]; } public AhoCorasickAutomaton () { root = new Node (); patternNodes = new ArrayList <>(); levelOrderNodes = new ArrayList <>(); } public void insert (String pattern) { Node node = root; for (int i = 0 ; i < pattern.length(); i++) { int idx = pattern.charAt(i) - 'a' ; if (node.next[idx] == null ) { node.next[idx] = new Node (); } node = node.next[idx]; } patternNodes.add(node); } public void build () { root.fail = root; Queue<Node> q = new LinkedList <>(); for (int i = 0 ; i < R; i++) { if (root.next[i] == null ) { root.next[i] = root; } else { root.next[i].fail = root; q.offer(root.next[i]); } } while (!q.isEmpty()) { Node node = q.poll(); levelOrderNodes.add(node); for (int i = 0 ; i < R; i++) { if (node.next[i] == null ) { node.next[i] = node.fail.next[i]; } else { node.next[i].fail = node.fail.next[i]; q.offer(node.next[i]); } } } } public void query (String text, int [] cnt) { Node node = root; for (int i = 0 ; i < text.length(); i++) { int idx = text.charAt(i) - 'a' ; node = node.next[idx]; node.cnt++; } for (int i = levelOrderNodes.size() - 1 ; i >= 0 ; i--) { node = levelOrderNodes.get(i); node.fail.cnt += node.cnt; } for (int i = 0 ; i < patternNodes.size(); i++) { cnt[i] += patternNodes.get(i).cnt; } } }
正则表达式
例题
实现
时间复杂度:构造 NFA \(O(m)\),匹配 \(O(nm)\),其中 \(m\) 为正则表达式的长度,\(n\) 为文本串的长度。
空间复杂度:构造 NFA \(O(m)\),匹配 \(O(m)\)。
1 System.out.println("TODO" );