Rolling Hash Search

Search for a pattern in a sequence by maintaining a hash over a sliding window.

Rolling Hash Search

Rolling hash search computes a hash over a fixed size window and updates it efficiently as the window moves. It allows substring search in linear time with low constant factors.

You use it when scanning large text for patterns or when building string matching algorithms such as Rabin Karp.

Problem

Given a text $T$ of length $n$ and a pattern $P$ of length $m$, find all indices $i$ such that:

$$ T[i \dots i + m - 1] = P $$

Model

Define a polynomial hash for a string:

$$ H(s_0 s_1 \dots s_{m-1}) = (s_0 \cdot B^{m-1} + s_1 \cdot B^{m-2} + \dots + s_{m-1}) \bmod M $$

For a sliding window, the hash updates as:

$$ H_{i+1} = \big((H_i - s_i \cdot B^{m-1}) \cdot B + s_{i+m}\big) \bmod M $$

This avoids recomputing the hash from scratch.

Algorithm

rolling_hash_search(T, P):
    n = length(T)
    m = length(P)

    target = hash(P)
    current = hash(T[0..m-1])

    for i from 0 to n - m:
        if current == target:
            if T[i..i+m-1] == P:
                report match at i

        if i < n - m:
            current = update_hash(current, T[i], T[i+m])

    return all matches

Example

Let:

$$ T = "abracadabra" $$

$$ P = "abra" $$

Sliding windows:

index substring hash match
0 abra yes
1 brac no
2 raca no
3 acad no
4 cada no
5 adab no
6 dabr no
7 abra yes

Matches at indices 0 and 7.

Correctness

The rolling hash ensures that every substring of length $m$ is assigned a hash value. When the hash matches the pattern hash, a direct comparison verifies the match.

The direct comparison eliminates false positives caused by hash collisions.

Complexity

operation time
preprocessing $O(m)$
search $O(n)$
verification depends on matches

Total time is $O(n + m)$ in expectation.

Space

$$ O(1) $$

Only a few integer values are stored.

When to Use

Rolling hash search is appropriate when:

  • substring matching is required
  • repeated hash computation must be avoided
  • large texts are processed
  • approximate matching is combined with verification
  • multiple pattern checks are needed

It is widely used in string matching, plagiarism detection, deduplication, and substring indexing.

Implementation

def rolling_hash_search(text, pattern):
    n, m = len(text), len(pattern)
    if m > n:
        return []

    B = 256
    M = 10**9 + 7

    def hash_str(s):
        h = 0
        for c in s:
            h = (h * B + ord(c)) % M
        return h

    target = hash_str(pattern)
    current = hash_str(text[:m])

    power = pow(B, m - 1, M)
    result = []

    for i in range(n - m + 1):
        if current == target:
            if text[i:i+m] == pattern:
                result.append(i)

        if i < n - m:
            current = (current - ord(text[i]) * power) % M
            current = (current * B + ord(text[i + m])) % M

    return result
func RollingHashSearch(text, pattern string) []int {
	n, m := len(text), len(pattern)
	if m > n {
		return nil
	}

	const B = 256
	const M = 1000000007

	hashStr := func(s string) int {
		h := 0
		for i := 0; i < len(s); i++ {
			h = (h*B + int(s[i])) % M
		}
		return h
	}

	target := hashStr(pattern)
	current := hashStr(text[:m])

	power := 1
	for i := 0; i < m-1; i++ {
		power = (power * B) % M
	}

	var result []int

	for i := 0; i <= n-m; i++ {
		if current == target && text[i:i+m] == pattern {
			result = append(result, i)
		}

		if i < n-m {
			current = (current - int(text[i])*power%M + M) % M
			current = (current*B + int(text[i+m])) % M
		}
	}

	return result
}