Repeated String Match (Rabin Karp)
Problem Statement:
Given two strings a and b, return the minimum number of times you should repeat string a so that string b is a substring of it. If it is impossible for b to be a substring of a after repeating it, return -1.
Notice: string "abc" repeated 0 times is "", repeated 1 time is "abc" and repeated 2 times is "abcabc".
-
Example:
Example 1:
Input: a = "abcd", b = "cdabcdab"
Output: 3
Explanation: We return 3 because by repeating a three times "abcdabcdabcd", b is a substring of it.Example 2:
Input: a = "a", b = "aa"
Output: 2
Solution: Rabin–Karp (Rolling Hash)
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
static constexpr long long MOD = 1000000007LL; // large prime modulus
static constexpr long long BASE = 31LL; // base for polynomial rolling hash
int repeatedStringMatch(string a, string b) {
if (a == b) return 1;
// Step 1: Build minimal repeated string such that length >= |b|
int count = 1;
string source = a;
while ((int)source.size() < (int)b.size()) {
source += a;
count++;
}
// Step 2: Check substring existence
if (rabinKarpFind(source, b) != -1) return count;
if (rabinKarpFind(source + a, b) != -1) return count + 1;
return -1;
}
private:
// Rabin–Karp substring search
int rabinKarpFind(const string &source, const string &target) {
int n = source.size(), m = target.size();
if (m == 0 || n < m) return -1;
// Precompute base^m % MOD
long long basePow = 1;
for (int i = 0; i < m; i++) basePow = (basePow * BASE) % MOD;
// Hash of target string
long long targetHash = 0;
for (int i = 0; i < m; i++) {
targetHash = (targetHash * BASE + (unsigned char)target[i]) % MOD;
}
// Rolling hash for source
long long windowHash = 0;
for (int i = 0; i < n; i++) {
windowHash = (windowHash * BASE + (unsigned char)source[i]) % MOD;
if (i < m - 1) continue; // wait until window is size m
// Remove leftmost char when window exceeds size m
if (i >= m) {
windowHash = (windowHash - (unsigned char)source[i - m] * basePow) % MOD;
if (windowHash < 0) windowHash += MOD;
}
// Compare hash & then strings to avoid collisions
if (windowHash == targetHash) {
int start = i - m + 1;
if (source.compare(start, m, target) == 0) return start;
}
}
return -1;
}
};
📝 How It Works
- Repeat string
auntil it becomes at least as long asb. - Check three cases:
- If
a == b, answer is1. - If
bis insidesource, answer iscount. - If
bspans across the boundary, checksource + a, answer iscount+1. - Otherwise, return
1.
- If
- Substring search is done using Rabin–Karp:
- Compute rolling hash for each substring of length
m = |b|. - Maintain hash efficiently by multiplying with
BASEand subtracting the outgoing character ×BASE^m. - If hashes match, verify with direct comparison.
- Compute rolling hash for each substring of length
🧩 Key Formula / Recurrence
- Rolling hash update:
- Here
BASE = 31,MOD = 1e9+7.
⏱️ Time & Space Complexity
- Time Complexity:
- Repetition of
a: O(|a| × ceil(|b|/|a|)). - Rabin–Karp search: O(|source| + |b|).
- Overall ≈ O(|a| + |b|).
- Repetition of
- Space Complexity: O(|source|) due to repeated string storage.
⚠️ Edge Cases
a == bdirectly returns 1.blonger thanaand spanning across repetitions.- Collisions in Rabin–Karp: handled by string comparison.
- Empty string case: handled by returning -1 when
n < m.
💡 Other Approaches
-
KMP (Knuth–Morris–Pratt):
Deterministic O(n+m) substring search, no hashing/collision risk.
-
Built-in
string::find:Simple and fast in practice (
(source+b).find(b)check). -
Naïve substring search: O(n·m), works for small input but inefficient.
🔁 Related Problems
- LeetCode 686 – Repeated String Match (this problem).
- LeetCode 28 – Implement strStr() (KMP / Rabin–Karp).
- LeetCode 459 – Repeated Substring Pattern.
💬