function rabinKarp(text, pattern) { const n = text.length; const m = pattern.length; if (m > n) return []; const base = 256; // Number of characters in alphabet const mod = 1e9 + 7; // Calculate base^(m-1) % mod let basePower = 1; for (let i = 0; i < m - 1; i++) { basePower = (basePower * base) % mod; } // Calculate hash of pattern and first window let patternHash = 0; let textHash = 0; for (let i = 0; i < m; i++) { patternHash = (patternHash * base + pattern.charCodeAt(i)) % mod; textHash = (textHash * base + text.charCodeAt(i)) % mod; } const result = []; // Slide window over text for (let i = 0; i <= n - m; i++) { // Check hash match if (patternHash === textHash) { // Verify actual characters (handle collisions) let match = true; for (let j = 0; j < m; j++) { if (text[i + j] !== pattern[j]) { match = false; break; } } if (match) result.push(i); } // Calculate next window hash (if not last window) if (i < n - m) { textHash = (textHash - text.charCodeAt(i) * basePower) % mod; textHash = (textHash * base + text.charCodeAt(i + m)) % mod; // Handle negative values if (textHash < 0) textHash += mod; } } return result;}// Example: text = "ababcababd", pattern = "abab"// Pattern hash calculated once// Text hash rolls through each position// Matches at indices: [0, 5]// Time: O(n + m) average case// Worst case: O(nm) with many collisions
Example: Simplified rolling hash update
// Understanding the rolling hash formula:// // Original hash for window [i, i+m-1]:// hash = c[i]*b^(m-1) + c[i+1]*b^(m-2) + ... + c[i+m-1]*b^0// // Next window [i+1, i+m]:// 1. Remove first character: hash - c[i]*b^(m-1)// 2. Shift left (multiply by b): (hash - c[i]*b^(m-1)) * b// 3. Add new character: result + c[i+m]// // new_hash = (old_hash - c[i]*b^(m-1)) * b + c[i+m]function updateHash(oldHash, oldChar, newChar, basePower, base, mod) { // Remove contribution of first character let hash = (oldHash - oldChar * basePower) % mod; // Multiply by base (shift left) hash = (hash * base) % mod; // Add new character hash = (hash + newChar) % mod; // Handle negative values if (hash < 0) hash += mod; return hash;}// Example: "abc" -> "bcd" with base=10// hash("abc") = 'a'*100 + 'b'*10 + 'c'*1 = 97*100 + 98*10 + 99// Remove 'a': hash - 97*100// Shift: (hash - 97*100) * 10// Add 'd': result + 100// hash("bcd") = 'b'*100 + 'c'*10 + 'd'*1
2. Repeated DNA Sequences Problem
Approach
Method
Time
Space
Rolling Hash + Set
Hash all 10-char substrings, track seen
O(n)
O(n)
Bit Encoding
2 bits per nucleotide, 20-bit integer
O(n)
O(n)
String Hash
Use string as key directly
O(n * 10)
O(n)
Optimization
Only 4 chars: A,C,G,T - perfect for bit encoding
Most efficient
Recommended
Example: Rolling hash approach
function findRepeatedDnaSequences(s) { if (s.length <= 10) return []; const seen = new Set(); const repeated = new Set(); const base = 4; // 4 nucleotides const mod = 1e9 + 7; const L = 10; // Sequence length // Map characters to numbers const charToNum = { 'A': 0, 'C': 1, 'G': 2, 'T': 3 }; // Calculate base^(L-1) let basePower = 1; for (let i = 0; i < L - 1; i++) { basePower = (basePower * base) % mod; } // Calculate hash of first window let hash = 0; for (let i = 0; i < L; i++) { hash = (hash * base + charToNum[s[i]]) % mod; } seen.add(hash); // Rolling hash for remaining windows for (let i = L; i < s.length; i++) { // Remove first char, add new char hash = (hash - charToNum[s[i - L]] * basePower) % mod; hash = (hash * base + charToNum[s[i]]) % mod; if (hash < 0) hash += mod; if (seen.has(hash)) { repeated.add(s.substring(i - L + 1, i + 1)); } seen.add(hash); } return Array.from(repeated);}// Example: s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT"// Result: ["AAAAACCCCC","CCCCCAAAAA"]
Example: Bit encoding optimization
function findRepeatedDnaBitEncoding(s) { if (s.length <= 10) return []; const seen = new Set(); const repeated = new Set(); const L = 10; // 2 bits per character: A=00, C=01, G=10, T=11 const charToBits = { 'A': 0, 'C': 1, 'G': 2, 'T': 3 }; // Build first window (20 bits total) let bitmask = 0; for (let i = 0; i < L; i++) { bitmask = (bitmask << 2) | charToBits[s[i]]; } seen.add(bitmask); // Mask to keep only last 20 bits const mask = (1 << (L * 2)) - 1; for (let i = L; i < s.length; i++) { // Shift left 2, add new char, keep 20 bits bitmask = ((bitmask << 2) | charToBits[s[i]]) & mask; if (seen.has(bitmask)) { repeated.add(s.substring(i - L + 1, i + 1)); } seen.add(bitmask); } return Array.from(repeated);}// Bit encoding is cleaner and faster:// - No modulo operations// - No multiplication// - Just bit shifts and masks// - 10 chars * 2 bits = 20 bits (fits in integer)
3. Longest Duplicate Substring Problem
Approach
Strategy
Time
Space
Binary Search + Hash
Binary search on length, hash to find duplicates
O(n log n)
O(n)
Suffix Array
Build suffix array, find LCP
O(n log n)
O(n)
Rolling Hash
For each length, check all substrings
O(n^2) naive
Not optimal alone
Optimal
Binary search + rolling hash combination
Best practical solution
Recommended
Example: Binary search with rolling hash
function longestDupSubstring(s) { const n = s.length; const base = 256; const mod = 2**53 - 1; // Large prime // Binary search on length let left = 1, right = n - 1; let result = ""; while (left <= right) { const mid = Math.floor((left + right) / 2); const duplicate = searchDuplicate(s, mid, base, mod); if (duplicate !== null) { result = duplicate; left = mid + 1; // Try longer } else { right = mid - 1; // Try shorter } } return result;}function searchDuplicate(s, L, base, mod) { // Calculate base^L % mod let basePower = 1; for (let i = 0; i < L; i++) { basePower = (basePower * base) % mod; } basePower = (basePower / base) % mod; // Calculate hash of first window let hash = 0; for (let i = 0; i < L; i++) { hash = (hash * base + s.charCodeAt(i)) % mod; } const seen = new Map(); seen.set(hash, 0); // Rolling hash for (let i = L; i < s.length; i++) { hash = (hash - s.charCodeAt(i - L) * basePower) % mod; hash = (hash * base + s.charCodeAt(i)) % mod; if (hash < 0) hash += mod; if (seen.has(hash)) { // Found duplicate, verify const start = seen.get(hash); const current = s.substring(i - L + 1, i + 1); const prev = s.substring(start, start + L); if (current === prev) { return current; } } seen.set(hash, i - L + 1); } return null;}// Example: s = "banana"// Binary search tries lengths: 3, 2, 1// Length 3: "ana" found// Length 4: none// Result: "ana"// Time: O(n log n) - log n binary search, O(n) per search// Space: O(n) for hash map
Note: The binary search optimization works because if there's a duplicate of length k, there's also a duplicate of length k-1 (monotonic property). This allows us to binary search on the length instead of trying all lengths sequentially.
4. Find All Anagrams in String
Approach
Method
Time
Space
Frequency Counter
Compare char frequencies in sliding window
O(n)
O(1) (26 letters)
Sorted Hash
Sort pattern and each window, compare
O(n * m log m)
Not efficient
Rolling Hash
Hash must be order-independent
O(n)
O(1)
Polynomial Hash
XOR or addition (commutative operations)
For anagrams
Order doesn't matter
Example: Frequency array sliding window
function findAnagrams(s, p) { const result = []; if (s.length < p.length) return result; const pCount = new Array(26).fill(0); const sCount = new Array(26).fill(0); // Count frequencies in pattern for (const char of p) { pCount[char.charCodeAt(0) - 'a'.charCodeAt(0)]++; } // First window for (let i = 0; i < p.length; i++) { sCount[s.charCodeAt(i) - 'a'.charCodeAt(0)]++; } if (arraysEqual(pCount, sCount)) { result.push(0); } // Sliding window for (let i = p.length; i < s.length; i++) { // Add new character sCount[s.charCodeAt(i) - 'a'.charCodeAt(0)]++; // Remove old character sCount[s.charCodeAt(i - p.length) - 'a'.charCodeAt(0)]--; if (arraysEqual(pCount, sCount)) { result.push(i - p.length + 1); } } return result;}function arraysEqual(a, b) { for (let i = 0; i < 26; i++) { if (a[i] !== b[i]) return false; } return true;}// Example: s = "cbaebabacd", p = "abc"// Pattern count: [1,1,1,0,...,0]// Result: [0,6] (indices of "cba" and "bac")
Example: Optimized with match counter
function findAnagramsOptimized(s, p) { const result = []; if (s.length < p.length) return result; const count = new Array(26).fill(0); // Difference array: positive = need more, negative = have extra for (const char of p) { count[char.charCodeAt(0) - 'a'.charCodeAt(0)]++; } let matches = 0; // First window for (let i = 0; i < p.length; i++) { const idx = s.charCodeAt(i) - 'a'.charCodeAt(0); count[idx]--; if (count[idx] === 0) matches++; else if (count[idx] === -1) matches--; } if (matches === 26) result.push(0); // Sliding window for (let i = p.length; i < s.length; i++) { // Add new character const newIdx = s.charCodeAt(i) - 'a'.charCodeAt(0); count[newIdx]--; if (count[newIdx] === 0) matches++; else if (count[newIdx] === -1) matches--; // Remove old character const oldIdx = s.charCodeAt(i - p.length) - 'a'.charCodeAt(0); count[oldIdx]++; if (count[oldIdx] === 0) matches++; else if (count[oldIdx] === 1) matches--; if (matches === 26) { result.push(i - p.length + 1); } } return result;}// Optimization: track how many characters have correct frequency// Only compare one counter instead of 26 each time// O(1) comparison vs O(26) = O(1)
5. Polynomial Rolling Hash Function
Property
Formula
Use Case
Characteristic
Standard Polynomial
hash = Σ(c[i] * p^i) mod m
String matching, order matters
Position-dependent
Commutative Hash
hash = Σ(c[i]) or XOR(c[i])
Anagrams, order doesn't matter
Position-independent
Multiple Hashes
Use 2+ hash functions with different bases/mods
Reduce collision probability
More reliable
Base Selection
Prime > alphabet size (31, 37, 101, 256)
Distribution quality
Critical choice
Example: Double hash to reduce collisions
class DoubleRollingHash { constructor(s, L) { this.s = s; this.L = L; // Two different hash functions this.base1 = 31; this.mod1 = 1e9 + 7; this.base2 = 37; this.mod2 = 1e9 + 9; // Precompute base powers this.basePower1 = 1; this.basePower2 = 1; for (let i = 0; i < L - 1; i++) { this.basePower1 = (this.basePower1 * this.base1) % this.mod1; this.basePower2 = (this.basePower2 * this.base2) % this.mod2; } } findDuplicates() { const seen = new Map(); // Calculate initial hashes let hash1 = 0, hash2 = 0; for (let i = 0; i < this.L; i++) { const code = this.s.charCodeAt(i); hash1 = (hash1 * this.base1 + code) % this.mod1; hash2 = (hash2 * this.base2 + code) % this.mod2; } const key = `${hash1},${hash2}`; seen.set(key, 0); // Rolling hash for (let i = this.L; i < this.s.length; i++) { const oldCode = this.s.charCodeAt(i - this.L); const newCode = this.s.charCodeAt(i); // Update hash1 hash1 = (hash1 - oldCode * this.basePower1) % this.mod1; hash1 = (hash1 * this.base1 + newCode) % this.mod1; if (hash1 < 0) hash1 += this.mod1; // Update hash2 hash2 = (hash2 - oldCode * this.basePower2) % this.mod2; hash2 = (hash2 * this.base2 + newCode) % this.mod2; if (hash2 < 0) hash2 += this.mod2; const key = `${hash1},${hash2}`; if (seen.has(key)) { // Extremely low probability of false positive return this.s.substring(i - this.L + 1, i + 1); } seen.set(key, i - this.L + 1); } return null; }}// Probability of collision with double hash:// P(collision) ≈ 1 / (mod1 * mod2) ≈ 10^-18// Practically zero for reasonable input sizes
Example: Polynomial hash implementation details
// Standard polynomial rolling hashclass PolynomialHash { constructor(base = 31, mod = 1e9 + 7) { this.base = base; this.mod = mod; } // Compute hash of entire string computeHash(s) { let hash = 0; for (let i = 0; i < s.length; i++) { hash = (hash * this.base + s.charCodeAt(i)) % this.mod; } return hash; } // Compute hash with precomputed powers (more efficient) computeHashPowers(s, powers) { let hash = 0; const n = s.length; for (let i = 0; i < n; i++) { hash = (hash + s.charCodeAt(i) * powers[n - 1 - i]) % this.mod; } return hash; } // Precompute powers of base precomputePowers(maxLen) { const powers = [1]; for (let i = 1; i < maxLen; i++) { powers[i] = (powers[i - 1] * this.base) % this.mod; } return powers; } // Rolling update: remove first, add last roll(hash, oldChar, newChar, basePower) { hash = (hash - oldChar * basePower) % this.mod; hash = (hash * this.base + newChar) % this.mod; if (hash < 0) hash += this.mod; return hash; }}// Usage:const hasher = new PolynomialHash();const powers = hasher.precomputePowers(1000);const hash1 = hasher.computeHashPowers("hello", powers);const hash2 = hasher.computeHashPowers("world", powers);// Different strings -> different hashes (with high probability)
Warning: Hash collisions can occur! Always verify actual string equality when hashes match, especially in Rabin-Karp. Use double hashing (two different hash functions) to reduce collision probability to near-zero for most practical applications. Never rely on hash equality alone for correctness.
Summary: Rolling Hash Patterns
Core Formula: hash = Σ(char[i] × base^(n-1-i)) mod m - converts string to number for O(1) comparison
Rolling Update: new_hash = (old_hash - first_char × base^(L-1)) × base + new_char - O(1) window slide
Rabin-Karp: Pattern matching with rolling hash - O(n+m) average, O(nm) worst case with collisions
Base Selection: Use prime > alphabet size (31 for lowercase, 256 for ASCII) - reduces collisions
Modulo Selection: Large prime like 10^9+7 or 10^9+9 - prevents integer overflow while maintaining distribution
Collision Handling: When hashes match, verify actual strings - hash equality doesn't guarantee string equality
DNA Sequences: Only 4 chars (A,C,G,T) - can use 2-bit encoding instead of polynomial hash for efficiency
Bit Encoding: bitmask = (bitmask << 2 | newChar) & mask - simpler than polynomial, no modulo needed
Longest Duplicate: Binary search on length + rolling hash to check - O(n log n) time
Binary Search Insight: If duplicate of length k exists, duplicate of k-1 also exists - monotonic property enables binary search
Anagrams: Need order-independent hash - use frequency array or commutative hash (sum/XOR), not polynomial
Double Hashing: Use two hash functions with different base/mod - collision probability ≈ 1/(mod1×mod2) ≈ 10^-18
Precompute Powers: Calculate base^i for all i - avoids repeated exponentiation, O(1) hash computation