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".

Thinking Process

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".

  • Identify the pattern from constraints (sorted? graph? optimal substructure?).
  • Write brute force first mentally, then optimize the bottleneck.
  • Verify edge cases: empty input, single element, duplicates.
Array + hash map 2 7 11 map hash map for O(1) lookups

Common Approaches

Typical techniques for this pattern:

Approach Time Space Notes
Brute force (this problem) Often O(n^2) or O(2^n) O(n) Baseline; clarifies the optimization target
Sort + scan O(n log n) O(1) Pairs, intervals, greedy ordering
Hash map / set O(n) O(n) Frequency, membership, two-sum style
Single-pass linear O(n) O(1) Two pointers, sliding window, Kadane

Examples

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

Example 3:

Input: a = "a", b = "a"
Output: 1

Example 4:

Input: a = "abc", b = "wxyz"
Output: -1

Constraints

  • 1 <= a.length, b.length <= 10^4
  • a and b consist of lowercase English letters.

String Matching Algorithms

Knuth-Morris-Pratt (KMP) Algorithm

KMP is an efficient string-searching algorithm that preprocesses the pattern to create a prefix function (also called LPS - Longest Proper Prefix which is also a Suffix). This allows skipping unnecessary comparisons.

Key Concepts:

  1. Prefix Function (π/LPS): For each position i in pattern, π[i] is the length of the longest proper prefix that is also a suffix of pattern[0..i]
  2. No Backtracking: When a mismatch occurs, we don’t reset to the beginning but use the prefix function to determine the next position
  3. Time Complexity: O(n + m) where n = text length, m = pattern length

How KMP Works:

  1. Preprocessing Phase: Build prefix function for pattern
    • For each position, find longest prefix-suffix match
    • Use previous values to compute current value efficiently
  2. Search Phase: Match pattern in text
    • Compare characters from left to right
    • On mismatch, use prefix function to skip ahead
    • Never backtrack in text

Prefix Function Example:

For pattern "ababaca":

Pattern:  a  b  a  b  a  c  a
Index:     0  1  2  3  4  5  6
π[i]:     0  0  1  2  3  0  1

Explanation:
- π[0] = 0 (no proper prefix)
- π[1] = 0 ("ab" has no prefix-suffix match)
- π[2] = 1 ("aba" has "a" as prefix-suffix)
- π[3] = 2 ("abab" has "ab" as prefix-suffix)
- π[4] = 3 ("ababa" has "aba" as prefix-suffix)
- π[5] = 0 ("ababac" has no prefix-suffix match)
- π[6] = 1 ("ababaca" has "a" as prefix-suffix)

Rabin-Karp Algorithm

Rabin-Karp uses rolling hash to efficiently compute hash values for substrings. It compares hash values first, then verifies with character-by-character comparison if hashes match.

Key Concepts:

  1. Rolling Hash: Compute hash of substring in O(1) time using previous hash
  2. Hash Function: Use polynomial rolling hash: hash = (hash * base + char) % mod
  3. Collision Handling: When hashes match, verify with actual string comparison
  4. Time Complexity: Average O(n + m), worst case O(n × m) if many hash collisions

How Rabin-Karp Works:

  1. Precompute Pattern Hash: Calculate hash of pattern string
  2. Rolling Hash in Text:
    • Compute hash of first window
    • Slide window and update hash in O(1)
    • Compare hashes, verify if match
  3. Base and Modulo: Use large base and prime modulo to reduce collisions

Rolling Hash Formula:

For substring s[i..i+m-1]:
hash = (s[i] * base^(m-1) + s[i+1] * base^(m-2) + ... + s[i+m-1]) % mod

To slide window from i to i+1:
new_hash = ((old_hash - s[i] * base^(m-1)) * base + s[i+m]) % mod

KMP Template

Here’s the general template for KMP algorithm:

class Solution {
        public int strStr(String haystack, String needle) {
        int n = haystack.size(), m = needle.size();
        if(m == 0) return 0;
        int[] pi = new int[m];
        for(int i = 1, j = 0; i < m; i++) {
            while(j > 0 && needle[i] != needle[j]) {
                j = pi[j - 1];
            }
            if(needle[i] == needle[j]) {
                j++;
            }
            pi[i] = j;
        }
        for(int i = 0, j = 0; i - j < n; i++) {
            while(j > 0 && haystack[i % n] != needle[j]) {
                j = pi[j - 1];
            }
            if(haystack[i % n] == needle[j]) {
                j++;
            }
            if(j == m) {
                return i - m + 1;
            }
        }
        return -1;
    }
        public int repeatedStringMatch(String a, String b) {
        int an = a.size(), bn = b.size();
        int idx = strStr(a, b);
        if(idx == -1) return -1;
        if(an - idx >= bn) {
            return 1;
        }
        return (bn + idx - an - 1) / an + 2;
    }
}

Key Template Components:

  1. Prefix Function (π/LPS):
    • pi[i] = longest prefix-suffix length for pattern[0..i]
    • Built in O(m) time
  2. Search Algorithm:
    • No backtracking in text
    • Use prefix function to skip on mismatch
    • Time complexity: O(n + m)
  3. Circular Matching:
    • Use i % n for circular text
    • Adjust loop condition: i - j < n

Rabin-Karp Template

Here’s the general template for Rabin-Karp algorithm:

class Solution {
        long BASE = 256;
    long MOD = 1e9 + 7;
        public long computeHash(String s, int start, int len) {
        long hash = 0;
        for(int i = 0; i < len; i++) {
            hash = (hash BASE + s[start + i]) % MOD;
        }
        return hash;
    }
        public long updateHash(long oldHash, char remove, char add, long power) {
        oldHash = (oldHash - (remove power) % MOD + MOD) % MOD;
        oldHash = (oldHash BASE + add) % MOD;
        return oldHash;
    }
        public boolean verifyMatch(String text, int start, String pattern) {
        for(int i = 0; i < pattern.size(); i++) {
            if(text[start + i] != pattern[i]) return false;
        }
        return true;
    }
        public int rabinKarpSearch(String text, String pattern, boolean circular) {
        int n = text.size(), m = pattern.size();
        if(m == 0) return 0;
        if(n < m) return -1;

        // Compute pattern hash
        long patternHash = computeHash(pattern, 0, m);

        // Compute power for rolling hash
        long power = 1;
        for(int i = 0; i < m - 1; i++) {
            power = (power BASE) % MOD;
        }

        // Compute initial window hash
        long textHash = computeHash(text, 0, m);

        // Check first window
        if(textHash == patternHash && verifyMatch(text, 0, pattern)) {
            return 0;
        }

        // Rolling hash
        int maxIterations = circular ? n + m - 1 : n - m + 1;
        for(int i = 1; i < maxIterations; i++) {
            int removeIdx = (i - 1) % n;
            int addIdx = (i + m - 1) % n;

            textHash = updateHash(textHash, text.charAt(removeIdx), text.charAt(addIdx), power);

            if(textHash == patternHash) {
                int start = i % n;
                if(verifyMatch(text, start, pattern)) {
                    return i;
                }
            }
        }

        return -1;
    }
        public int repeatedStringMatch(String a, String b) {
        int an = a.size(), bn = b.size();

        // Try circular search
        int idx = rabinKarpSearch(a, b, true);
        if(idx == -1) return -1;

        if(an - idx >= bn) {
            return 1;
        }
        return (bn + idx - an - 1) / an + 2;
    }
}

Key Template Components:

  1. Hash Function:
    • Polynomial rolling hash: hash = (hash * BASE + char) % MOD
    • BASE = 256 (for ASCII), MOD = large prime
  2. Rolling Hash:
    • Update hash in O(1) when sliding window
    • Remove left char, add right char
  3. Collision Handling:
    • Always verify hash matches with actual string comparison
    • Prevents false positives

Complexity

Solution 1: KMP

Time Complexity: O(n + m)

  • Prefix function: O(m) - build LPS array
  • Search phase: O(n) - each character visited at most twice
  • Total: O(n + m) where n = a.length, m = b.length

Space Complexity: O(m)

  • Prefix function array: O(m)
  • Total: O(m)

Solution 2: Rabin-Karp

Time Complexity: O(n + m) average, O(n × m) worst case

  • Hash computation: O(m) for pattern
  • Rolling hash: O(n) for text (O(1) per window)
  • Verification: O(m) per hash match (rare collisions)
  • Total: O(n + m) average, O(n × m) worst case with many collisions

Space Complexity: O(1)

  • Hash variables: O(1)
  • Total: O(1) excluding input strings

Key Points

  1. KMP is Optimal: Guaranteed O(n + m) time, no worst-case degradation
  2. Rabin-Karp: Average O(n + m), but can degrade with hash collisions
  3. Circular Matching: Use modulo arithmetic for repeated strings
  4. Minimum Repetitions: At least ⌈b.length / a.length⌉, at most ⌈b.length / a.length⌉ + 1
  5. Prefix Function: Key to KMP’s efficiency - avoids backtracking
  6. Rolling Hash: Key to Rabin-Karp’s efficiency - O(1) hash updates

Comparison: KMP vs Rabin-Karp

Aspect KMP Rabin-Karp
Time Complexity O(n + m) guaranteed O(n + m) average, O(n × m) worst
Space Complexity O(m) O(1)
Preprocessing O(m) for prefix function O(m) for pattern hash
Backtracking None (no text backtracking) None (sliding window)
Collision Handling Not needed Required (verify matches)
Implementation More complex Simpler
Recommended ✅ Yes (guaranteed performance) ⚠️ Good for average case

Key Takeaways

  • Notice:** String "abc" repeated 0 times is "", repeated 1 time is "abc", and repeated 2 times is "abcabc".
  • Identify the pattern from constraints (sorted? graph? optimal substructure?).
  • Write brute force first mentally, then optimize the bottleneck.

References

Common Mistakes

  • Skipping edge cases (empty input, single element, boundaries).
  • Off-by-one errors in loops and index ranges.
  • Forgetting to handle the case when no valid answer exists.

Tags

String Matching, KMP, Knuth-Morris-Pratt, Rabin-Karp, Rolling Hash, Prefix Function, Medium