Rabin Karp String Matching in DSA Typescript - Time & Space Complexity
We want to understand how the Rabin Karp algorithm's running time changes as the text and pattern sizes grow.
Specifically, how many steps does it take to find a pattern inside a text?
Analyze the time complexity of the following Rabin Karp code snippet.
function rabinKarp(text: string, pattern: string): number[] {
const result: number[] = [];
const base = 256;
const prime = 101;
const m = pattern.length;
const n = text.length;
let patternHash = 0, textHash = 0, h = 1;
for (let i = 0; i < m - 1; i++) h = (h * base) % prime;
for (let i = 0; i < m; i++) {
patternHash = (base * patternHash + pattern.charCodeAt(i)) % prime;
textHash = (base * textHash + text.charCodeAt(i)) % prime;
}
for (let i = 0; i <= n - m; i++) {
if (patternHash === textHash) {
let match = true;
for (let j = 0; j < m; j++) {
if (text[i + j] !== pattern[j]) {
match = false;
break;
}
}
if (match) result.push(i);
}
if (i < n - m) {
textHash = (base * (textHash - text.charCodeAt(i) * h) + text.charCodeAt(i + m)) % prime;
if (textHash < 0) textHash += prime;
}
}
return result;
}
This code finds all positions where the pattern appears in the text using hashing and sliding window.
Look for loops and repeated checks:
- Primary operation: The outer loop slides over the text (up to n - m + 1 times).
- Inner loop: When hashes match, it checks characters one by one (up to m times).
- Hash computations: Calculated once for pattern and updated for each window.
As the text length (n) grows, the outer loop runs roughly n times. The inner character check runs only when hashes match.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 hash updates, few character checks |
| 100 | About 100 hash updates, some character checks |
| 1000 | About 1000 hash updates, character checks depend on collisions |
Pattern length (m) affects character checks only when hashes match. Usually, checks are rare, so growth is close to linear with text size.
Time Complexity: O(n + m) on average, O(n * m) in worst case
This means usually the algorithm runs quickly proportional to text and pattern size, but in rare cases it can slow down if many hash collisions happen.
[X] Wrong: "Rabin Karp always runs in O(n) time no matter what."
[OK] Correct: If many hash collisions occur, the algorithm must check characters often, causing slower O(n * m) time.
Understanding Rabin Karp's time complexity shows you how hashing helps speed up searching, and how worst cases still matter. This skill helps you explain trade-offs clearly in interviews.
"What if we used a larger prime number for hashing? How would that affect the time complexity?"
Practice
Rabin Karp String Matching algorithm?Solution
Step 1: Understand the algorithm's approach
The Rabin Karp algorithm uses a hash function to convert strings into numbers for quick comparison.Step 2: Identify the benefit of hashing
Hashing allows checking if substrings match the pattern by comparing hash values instead of characters directly.Final Answer:
Use hashing to find pattern matches efficiently -> Option AQuick Check:
Rabin Karp = hashing for matching [OK]
- Thinking it uses recursion
- Assuming it sorts strings before matching
- Confusing it with brute force checking
s of length m using a base d and a prime modulus q in TypeScript?Solution
Step 1: Understand the rolling hash formula
The hash is computed as hash = (hash * d + current_char) mod q for each character.Step 2: Match the code with the formula
let hash = 0; for(let i=0; iFinal Answer:
let hash = 0; for(let i=0; i -> Option AQuick Check:
Rolling hash = (prev_hash * d + char) % q [OK]
- Mixing up base and modulus in calculation
- Starting hash from 1 instead of 0
- Incorrect order of operations in formula
"abc" in text "ababcabc"?
const text = "ababcabc";
const pattern = "abc";
const d = 256;
const q = 101;
function rabinKarp(text: string, pattern: string): number[] {
const m = pattern.length;
const n = text.length;
let p = 0, t = 0, h = 1;
const result: number[] = [];
for (let i = 0; i < m - 1; i++) h = (h * d) % q;
for (let i = 0; i < m; i++) {
p = (d * p + pattern.charCodeAt(i)) % q;
t = (d * t + text.charCodeAt(i)) % q;
}
for (let i = 0; i <= n - m; i++) {
if (p === t) {
let match = true;
for (let j = 0; j < m; j++) {
if (text[i + j] !== pattern[j]) {
match = false;
break;
}
}
if (match) result.push(i);
}
if (i < n - m) {
t = (d * (t - text.charCodeAt(i) * h) + text.charCodeAt(i + m)) % q;
if (t < 0) t += q;
}
}
return result;
}
console.log(rabinKarp(text, pattern));Solution
Step 1: Identify pattern occurrences in text
The pattern "abc" appears starting at index 2 ("ababcabc") and index 5.Step 2: Verify code logic for matching indices
The code correctly finds these two positions and returns [2, 5].Final Answer:
[2, 5] -> Option DQuick Check:
Pattern found at indices 2 and 5 [OK]
- Including index 0 which is partial match
- Confusing overlapping matches
- Forgetting to adjust negative hash values
function rabinKarpBug(text: string, pattern: string): number[] {
const m = pattern.length;
const n = text.length;
const d = 256;
const q = 101;
let p = 0, t = 0, h = 1;
const result: number[] = [];
for (let i = 0; i < m; i++) h = (h * d) % q;
for (let i = 0; i < m; i++) {
p = (d * p + pattern.charCodeAt(i)) % q;
t = (d * t + text.charCodeAt(i)) % q;
}
for (let i = 0; i <= n - m; i++) {
if (p === t) {
let match = true;
for (let j = 0; j < m; j++) {
if (text[i + j] !== pattern[j]) {
match = false;
break;
}
}
if (match) result.push(i);
}
if (i < n - m) {
t = (d * (t - text.charCodeAt(i) * h) + text.charCodeAt(i + m)) % q;
if (t < 0) t += q;
}
}
return result;
}Solution
Step 1: Check computation of h
Variable h should be (d^(m-1)) % q, but loop runs m times, making h too large.Step 2: Understand impact on rolling hash
Incorrect h causes wrong subtraction in rolling hash, leading to incorrect matches.Final Answer:
The initial value of h is computed incorrectly; it should be for (i = 0; i < m-1; i++) -> Option BQuick Check:
h loop runs m-1 times, not m [OK]
- Calculating h with wrong loop range
- Forgetting modulo in hash updates
- Misindexing pattern or text in loops
"aaa" in text "aaaaaa". What is the output of the algorithm and why is it important to verify matches after hash comparison?Solution
Step 1: Identify all pattern occurrences in text
The pattern "aaa" appears starting at indices 0, 1, 2, and 3 in "aaaaaa" due to overlapping.Step 2: Explain importance of verifying matches after hash
Hash collisions can cause false positives, so character-by-character check confirms actual matches.Final Answer:
[0, 1, 2, 3], because overlapping matches exist and hash collisions can occur -> Option CQuick Check:
Overlapping matches + verify after hash = [0,1,2,3] [OK]
- Ignoring overlapping matches
- Assuming hash guarantees no false positives
- Counting only first or non-overlapping matches
Ready to prove it?
Pass the test and this pattern moves unknown
