Hello! Welcome to your next lesson on string algorithms.
In our last session, you mastered the concept of polynomial rolling hash. We saw how to convert strings into numerical fingerprints and, more importantly, how to update these hashes in O(1) time as we slide a window across a text. This powerful technique is the engine behind several efficient algorithms.
Today, we'll put that engine to work. Your learning outcome is to implement the Rabin-Karp algorithm for pattern matching. We will combine the rolling hash technique with a systematic search strategy to create a complete, practical algorithm for finding a pattern in a text. This is a classic algorithm you're likely to encounter in technical interviews and competitive programming, making it a valuable addition to your toolkit.
1. The Rabin-Karp Strategy: Hashing Meets Sliding Window
The core idea of the Rabin-Karp algorithm is to avoid the expensive character-by-character comparison of a naive search. Instead of comparing strings, we'll compare their hash values.
The overall strategy is as follows:
- Hash the Pattern: Calculate the hash of the pattern
Pyou're searching for. - Initial Window Hash: Calculate the hash of the first substring (or "window") of the text
Tthat has the same length as the pattern. - Slide and Compare: Slide the window across the text one character at a time. In each step:
- Compare the hash of the current text window with the hash of the pattern.
- If the hashes do not match, the strings cannot be equal, so we move on.
- If the hashes do match, it's a potential match. This could be a genuine match or a "spurious hit" (a hash collision). We must then perform a direct character-by-character comparison to verify. If it's a real match, we record the starting index.
- "Roll" the hash to the next window in
O(1)time.
This process is visualized well in the following image.

To get a clearer conceptual understanding, let's watch a short segment that introduces this idea.
9.2 Rabin-Karp String Matching Algorithm
The video 'Rabin-Karp String Matching Algorithm' by Abdul Bari provides a fantastic explanation of the algorithm's basic idea.
Please watch from the beginning to 4:50. This section explains the fundamental concept: converting substrings to numerical values (hashes) and comparing them, instead of comparing characters directly.
2. Spurious Hits and the Importance of a Good Hash Function
As the video you just watched demonstrates, a simple hash function (like summing character values) is easy to compute but can lead to many spurious hits—cases where the hashes match but the strings do not. This degrades performance because each spurious hit forces an expensive O(M) character-by-character check, where M is the pattern length.
Let's see an example of this worst-case scenario.
9.2 Rabin-Karp String Matching Algorithm
Abdul Bari's video continues by showing exactly how a poor hash function can lead to the worst-case performance.
Watch the segment from 7:23 to 11:15. Notice how the simple additive hash function results in a hash match at almost every step, defeating the purpose of hashing.
This is precisely why we use the polynomial rolling hash that you learned in the previous lesson. By weighting characters based on their position, we dramatically reduce the probability of collisions.
The formula we will use is:
3. The Rolling Mechanism in Action
The real power of Rabin-Karp comes from updating the hash in O(1) time. As a refresher, to move the window from T[i...i+M-1] to T[i+1...i+M], we:
- Subtract the value of the character leaving the window (
T[i]). - Shift the remaining hash value (multiply by the
base). - Add the value of the new character entering the window (
T[i+M]).
The formula for the new hash (hash_new) based on the old hash (hash_old) is:
This process is explained very clearly in the following segment.
9.2 Rabin-Karp String Matching Algorithm
Let's revisit Abdul Bari's video to see how this rolling mechanism works with the improved polynomial hash function.
Watch from 11:15 to 20:39. This is a key segment. The first part (until 13:55) introduces the polynomial hash formula. The second part (from 13:55 onwards) demonstrates the mathematical steps for the rolling update. Pay close attention to how he subtracts the most significant term, multiplies the result by the base, and adds the new least significant term.
A crucial point mentioned at the end of that clip is managing the size of the hash values. By performing all calculations modulo a large prime number (mod), we prevent integer overflow and keep the hashes manageable.
4. Java Implementation of Rabin-Karp
Now, let's bring everything together in a Java implementation. Your goal is to find all occurrences of a pattern in a text and return a list of their starting indices.
This implementation will follow the logic we've discussed:
- Set up constants for
baseandmod. - Pre-compute
base^(M-1)to facilitate theO(1)update. - Calculate initial hashes for the pattern and the first window of the text.
- Loop through the text, comparing hashes and verifying matches.
- Use the rolling hash formula to update the text window's hash in each iteration.
Rabin Karp Algorithm - Pattern Searching
The article 'Rabin Karp Algorithm - Pattern Searching' from takeuforward.org provides a clean, well-commented Java implementation that is perfect for study. It's a great example of how this algorithm is coded in a competitive programming context.
Study the Java code provided in the article. Focus on understanding these key parts: The variables base, mod, and h. What is h and why is it pre-computed? The initial loops that calculate patternHash and windowHash. The main sliding window loop (for (int i = 0; i <= n - m; i++)). The character-by-character check that happens only when patternHash == windowHash. The rolling hash calculation: windowHash = (base * (windowHash - text.charAt(i) * h) + text.charAt(i + m)) % mod;. The line if (windowHash < 0) windowHash += mod;. Why is this necessary?
Let's break down that tricky rolling hash update line from the code:windowHash = (base * (windowHash - text.charAt(i) * h) + text.charAt(i + m)) % mod;
text.charAt(i) * h: This is the contribution of the leftmost character,T[i].his pre-calculated asbase^(M-1) % mod.windowHash - ...: We subtract this contribution from the old hash.base * (...): We shift the remaining hash to the left.+ text.charAt(i + m): We add the new character's value.% mod: We take the modulus to keep the result in range. The check forwindowHash < 0is crucial because in Java, the%operator can return a negative result, which we must correct by addingmodto bring it back into the[0, mod-1]range.
Test your understanding!
What is the primary reason for the Rabin-Karp algorithm having a worst-case time complexity of O(N*M), even with a good polynomial hash function?
Show answer
The worst-case complexity of O(N*M) occurs when hash collisions happen frequently. If, by pure chance or due to a "maliciously" crafted input, the hash of the text window matches the pattern's hash at every (or many) positions, the algorithm is forced to perform a full character-by-character comparison (an O(M) operation) for each of the N windows. This negates the benefit of hashing and results in the same complexity as the naive brute-force approach.
5. Complexity Analysis
Let N be the length of the text and M be the length of the pattern.
-
Time Complexity:
- Average Case: O(N + M). This is the typical performance. We spend
O(M)to compute the initial hashes and thenO(N-M)to slide the window, with each step beingO(1)for the hash update and comparison. Collisions are rare, so the verification cost is minimal. - Worst Case: O(N * M). As discussed, this occurs with frequent hash collisions.
- Average Case: O(N + M). This is the typical performance. We spend
-
Space Complexity: O(1). The algorithm uses a constant amount of extra space for variables to store the hashes, indices, and constants, regardless of the input size.
Conclusion
You have now successfully implemented the Rabin-Karp algorithm, a clever and practical application of the rolling hash technique. By trading guaranteed worst-case performance for excellent average-case speed, it provides an efficient way to solve pattern matching problems.
Key Takeaways:
- Rabin-Karp combines the sliding window technique with polynomial rolling hash.
- It achieves an
O(1)average time for comparing the pattern against each text substring by comparing hashes. - A hash match is only a potential match; it must be verified with a character-by-character comparison to handle collisions.
- The algorithm's efficiency hinges on a good hash function that minimizes collisions.
- The time complexity is
O(N+M)on average but can degrade toO(N*M)in the worst case. The space complexity isO(1).
Preview of the next lesson:
While Rabin-Karp is very fast on average, its O(N*M) worst-case can be a concern. In our next lesson, we will begin our journey into another famous pattern matching algorithm: Knuth-Morris-Pratt (KMP). KMP is remarkable because it guarantees O(N+M) time complexity in all cases by using a clever pre-computation on the pattern to avoid redundant comparisons. We'll start by learning to build its core component: the Longest Proper Prefix which is also a Suffix (LPS) array.