Skip to main content

String Algorithms

String Problems in CP

String algorithms unlock pattern matching, palindrome detection, and text processing problems. The key is choosing the right tool. Why specialized algorithms? Naive string matching (check every position) is O(n*m). For n = m = 10^6, that is 10^12 operations—far too slow. KMP, Z-function, and hashing each bring this down to O(n + m) by cleverly reusing work from previous comparisons. The intuition behind all of them: when a mismatch occurs, you have already matched some characters, and that partial match tells you where to look next without starting over.
Pattern Recognition Signals:
  • “Find pattern in text” → KMP, Z-function, or Hashing
  • “Count occurrences of pattern” → KMP or Hashing
  • “Longest palindrome” → Manacher’s algorithm
  • “Prefix queries on dictionary” → Trie
  • “Compare substrings” → Hashing with binary search

Algorithm Selection Guide


String Hashing

Convert strings to numbers for fast comparison. The Problem: Comparing two strings of length n takes O(n). For many comparisons, this is too slow. The Insight: Map each string to a number (hash). If hashes differ, strings differ. If hashes match, strings are probably equal. How it works: Treat the string as a number in base B: H(s)=s[0]Bn1+s[1]Bn2+...+s[n1]B0(modMOD)H(s) = s[0] \cdot B^{n-1} + s[1] \cdot B^{n-2} + ... + s[n-1] \cdot B^0 \pmod{MOD} Example: “abc” with B=31: H=1312+2311+3310=961+62+3=1026H = 1 \cdot 31^2 + 2 \cdot 31^1 + 3 \cdot 31^0 = 961 + 62 + 3 = 1026 Substring Hash in O(1): Using prefix hashes: H(s[l..r])=H(s[0..r])H(s[0..l1])Brl+1H(s[l..r]) = H(s[0..r]) - H(s[0..l-1]) \cdot B^{r-l+1} Collision Risk: Two different strings can have the same hash. Use:
  • Large prime MOD (10^9 + 7)
  • Double hashing: Two different (BASE, MOD) pairs—collision probability becomes ~1/10^18
Contest edge case: On Codeforces, some problem setters specifically add anti-hash tests for single-hash solutions. If your hashing solution gets WA on a string problem, switching to double hashing (or using a different base/mod pair) often fixes it. Always prefer double hashing for problems with large test cases.

Double Hashing (Reduces Collision)


KMP Algorithm

Find all occurrences of pattern in text in O(n + m). The Problem: Naive search is O(n·m)—for each position in text, compare entire pattern. KMP’s Insight: When a mismatch occurs, we’ve already matched some characters. Use this information to skip ahead instead of starting over.

Computing Failure Function (LPS Array)

LPS[i] = length of the Longest Proper Prefix of pattern[0..i] which is also a Suffix. Example: pattern = “ABABAC”
Why this helps: When mismatch at position j, we know pattern[0..j-1] matched. The LPS tells us the longest prefix we don’t need to re-check.

Pattern Matching


Z-Function

z[i] = length of longest substring starting from i that matches a prefix of s. Example: s = “aabxaab”
The Optimization (Z-box technique): Maintain [l, r)—the rightmost segment that matches a prefix. If i < r, we can reuse previously computed values! Pattern Matching Trick: To find pattern P in text T:
  1. Create combined = P + ”"+T(" + T ( is a separator not in P or T)
  2. Compute Z-function
  3. Wherever z[i] == len(P), we found a match at position i - len(P) - 1 in T

Trie (Prefix Tree)

Efficient for prefix-based queries on a dictionary. Structure: A tree where:
  • Each edge is labeled with a character
  • Each node represents a prefix (path from root)
  • Words sharing a prefix share that path
Visual Example with words [“cat”, “car”, “card”, “dog”]:
Operations:
  • Insert/Search: O(length of word)
  • Count words with prefix: O(length of prefix)
  • Autocomplete: O(prefix length + number of suggestions)
When to use:
  • Dictionary with prefix queries
  • XOR maximization (binary trie)
  • Counting distinct prefixes

Pattern 1: Longest Palindromic Substring

Manacher’s Algorithm (O(n))

The Problem: Find all palindromes in O(n) time. The Trick: Insert ’#’ between characters to handle even-length palindromes uniformly.
  • “abba” becomes “#a#b#b#a#”
  • Now all palindromes have odd length with a center
The Optimization: Similar to Z-function, maintain the rightmost palindrome [c-r, c+r]. If i is inside this palindrome, we can use the mirror position 2c-i to get a starting value for p[i]. Visual:
Key Insight: p[i] in the transformed string equals the length of the palindrome centered at position i/2 in the original string.

Pattern 2: Distinct Substrings

Problem: Count number of distinct substrings.

Pattern 3: Longest Common Substring


Pattern 4: Aho-Corasick (Multiple Pattern Matching)

For searching multiple patterns simultaneously.

Common Mistakes

Mistake 1: Hash collision Use double hashing for important problems. Single hash can collide. On Codeforces, problem setters often add anti-hash tests that specifically target common single-hash parameters.
Mistake 2: Wrong base/mod
  • Base should be > alphabet size (26 for lowercase letters, so use 31+)
  • Mod should be large prime (10^9 + 7 or 10^9 + 9)
  • Using base = 26 and mapping ‘a’ to 0 is dangerous: all strings starting with ‘a’ have hash 0 for the first character, increasing collision risk
  • Safe mapping: s[i] - 'a' + 1 (so ‘a’ maps to 1, not 0)
Mistake 3: Negative hash values In C++, the % operator can return negative values for negative operands. Always add MOD before taking modulo:
Mistake 4: Off-by-one in substring hashing When computing hash of s[l..r] (inclusive), the formula uses power[r - l + 1] (the length of the substring). Double-check whether your getHash function uses inclusive or exclusive bounds — mismatches cause subtle bugs.

Practice Problems

Beginner (1000-1300)

Intermediate (1300-1600)

Advanced (1600-1900)


Key Takeaways

Hashing

O(1) substring comparison after O(n) preprocessing.

KMP/Z-function

O(n + m) pattern matching without false positives.

Trie

Efficient prefix queries and autocomplete.

Manacher

O(n) for all palindromic substrings.

Next Up

Chapter 19: Bit Manipulation

Master bitwise operations, bitmasks, and XOR tricks.