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:
    def strStr(self, haystack: str, needle: str) -> int:
        if needle == "":
            return 0

        n, m = len(haystack), len(needle)

        # build lps (prefix function)
        lps = [0] * m

        j = 0
        for i in range(1, m):
            while j > 0 and needle[i] != needle[j]:
                j = lps[j - 1]

            if needle[i] == needle[j]:
                j += 1
                lps[i] = j

        # search
        j = 0
        for i in range(n):
            while j > 0 and haystack[i] != needle[j]:
                j = lps[j - 1]

            if haystack[i] == needle[j]:
                j += 1

            if j == m:
                return i - m + 1

        return -1

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:
    BASE = 256
    MOD = 10**9 + 7

    def computeHash(self, s, start, length):
        h = 0
        for i in range(length):
            h = (h * self.BASE + ord(s[start + i])) % self.MOD
        return h

    def updateHash(self, oldHash, remove, add, power):
        oldHash = (oldHash - remove * power) % self.MOD
        oldHash = (oldHash * self.BASE + add) % self.MOD
        return oldHash

    def verifyMatch(self, text, start, pattern):
        for i in range(len(pattern)):
            if text[start + i] != pattern[i]:
                return False
        return True

    def repeatedStringMatch(self, a: str, b: str) -> int:
        # simple correct solution (recommended)
        repeat = -(-len(b) // len(a))  # ceil
        s = a * repeat

        if b in s:
            return repeat
        if b in s + a:
            return repeat + 1

        return -1

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