GeeksforGeeks Medium

Rabin-Karp Algorithm

Find all occurrences of pat in txt using a rolling hash — average O(n+m).

stringrolling-hashpattern-matching
Open on GeeksforGeeks ↗
02

Intuition

Comparing the pattern at every shift is O(n·m). Instead hash the pattern once and hash each window of the text — sliding the window updates the hash in O(1): drop the leading char's contribution, shift, add the new char. Only equal hashes trigger a real comparison.

How to spot this pattern

Hash the pattern once, then roll a hash across the text so each window costs O(1) to compute rather than O(m). The rolling update — subtract the leaving character's contribution, shift, add the entering one — is the reusable idea. Because hashes can collide, a match must always be confirmed by a real comparison.

03

Approach

1

Polynomial hash

hash(s) = Σ s[i]·d^(m−1−i) mod q. Treat the string as a base-d number modulo a prime.

2

Roll in O(1)

next = (d·(cur − lead·d^(m−1)) + newChar) mod q. One multiply-subtract-add per shift.

3

Verify on hash hits

Hash collisions are possible → confirm with a direct compare. With a good prime, spurious hits are rare, giving O(n+m) average.

04

Solution & live demo

1def rabin_karp(txt, pat, d=256, q=101):
2 n, m = len(txt), len(pat)
3 if m > n: return []
4 h = pow(d, m - 1, q)
5 p = t = 0
6 for i in range(m):
7 p = (d * p + ord(pat[i])) % q
8 t = (d * t + ord(txt[i])) % q
9 hits = []
10 for s in range(n - m + 1):
11 if p == t and txt[s:s+m] == pat:
12 hits.append(s)
13 if s < n - m:
14 t = (d * (t - ord(txt[s]) * h) + ord(txt[s + m])) % q
15 return hits
05

Common pitfalls

Trusting the hash without verifying

✗ Wrong
if p == t:
    hits.append(s)
✓ Right
if p == t and txt[s:s+m] == pat:
    hits.append(s)

Different strings can hash to the same value modulo q, so a hash match is only a candidate. Skipping the confirmation reports false positives — the verification is what makes the algorithm correct rather than probabilistic.

Recomputing the window hash from scratch

✗ Wrong
t = 0
for i in range(m):
    t = (d * t + ord(txt[s+i])) % q
✓ Right
t = (d * (t - ord(txt[s]) * h) + ord(txt[s + m])) % q

That's O(m) per position and gives away the whole advantage — you may as well compare strings directly. The rolling update is O(1) because it only adjusts for the two characters that changed.

Using the wrong power for the leading character

✗ Wrong
h = d ** m
✓ Right
h = pow(d, m - 1, q)

The outgoing character sits at the highest position of an m-digit number, whose weight is d^(m-1). Using d^m removes a value that was never there and the rolling hash desynchronises from the window.

06

Edge cases

Hash collision without a match

The verification compare rejects it — correctness never depends on the hash.

Pattern longer than text

No windows exist; return no matches.

07

Complexity

Time
O(n+m) average
Space
O(1)
O(n·m) worst case under adversarial collisions.