Facebook Pixel

686. Repeated String Match

MediumStringString Matching
Leetcode Link

Problem Description

You are given two strings a and b. Your task is to find the minimum number of times you need to repeat string a so that string b becomes a substring of the repeated version of a.

For example:

  • If a = "abc" and you repeat it 0 times, you get an empty string ""
  • If you repeat it 1 time, you get "abc"
  • If you repeat it 2 times, you get "abcabc"
  • If you repeat it 3 times, you get "abcabcabc"

The goal is to determine the smallest number of repetitions needed such that b appears as a substring within the repeated a. If it's impossible for b to ever become a substring of repeated a (no matter how many times you repeat), return -1.

The solution works by first calculating the theoretical minimum number of repetitions needed based on the lengths of the strings (ceil(len(b) / len(a))). This gives us the minimum number of copies of a needed to have at least as many characters as b.

Then it checks if b is a substring of the repeated string. If not found initially, it tries adding up to 2 more copies of a to handle cases where b might span across the boundaries of repeated a strings. The algorithm checks at most 3 additional repetitions beyond the theoretical minimum. If b is still not found as a substring after these attempts, it returns -1, indicating it's impossible.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

To understand why we need to repeat string a, let's think about when b can be a substring of repeated a.

First, we need at least enough characters to cover the length of b. If a = "abc" (length 3) and b = "abcabcab" (length 8), we need at least ceil(8/3) = 3 repetitions of a to have enough characters.

But here's the key insight: even if we have enough characters, b might start in the middle of one copy of a and end in the middle of another copy. For example, if a = "abcd" and b = "cdabcdab", then b starts with "cd" (the tail of one a) and ends with "ab" (the head of another a).

In the worst case, b could:

  1. Start near the end of one copy of a
  2. Span several complete copies of a in the middle
  3. End near the beginning of another copy of a

This means we might need up to 2 extra copies of a beyond the theoretical minimum to account for these boundary cases - one extra for the potential partial match at the beginning and one for the potential partial match at the end.

Therefore, the strategy is:

  • Start with the minimum number of repetitions: ceil(len(b) / len(a))
  • Check if b is a substring
  • If not, try adding one more copy of a (to handle boundary overlaps)
  • Keep trying up to 2-3 extra copies
  • If b still isn't found, it's impossible

The reason we only need to check a few extra repetitions is that if b isn't found within ceil(len(b) / len(a)) + 2 repetitions, it means b contains some pattern that doesn't exist in any repetition of a, making it impossible.

Solution Approach

The implementation follows a straightforward approach based on our intuition:

  1. Calculate the theoretical minimum repetitions:

    m, n = len(a), len(b)
    ans = ceil(n / m)

    We first get the lengths of both strings. The minimum number of repetitions needed is ceil(n / m), which ensures we have at least as many characters as b.

  2. Build the initial repeated string:

    t = [a] * ans

    We create a list containing ans copies of string a. Using a list here allows us to efficiently append more copies if needed.

  3. Check for substring match with additional attempts:

    for _ in range(3):
        if b in ''.join(t):
            return ans
        ans += 1
        t.append(a)

    We try up to 3 times:

    • First, check if b is a substring of the current repeated string by joining the list t into a single string and using Python's in operator
    • If b is found, return the current count ans
    • If not found, increment ans and append one more copy of a to our list
    • Repeat this process up to 3 times total
  4. Return -1 if no match found:

    return -1

    If after checking the minimum required repetitions plus 2 additional copies (3 attempts total), b is still not a substring, it's impossible for b to ever be a substring of repeated a.

The key design choices in this implementation:

  • Using a list to store copies of a allows efficient appending
  • The in operator provides a clean way to check substring existence
  • Limiting to 3 additional attempts is sufficient because if b requires more than ceil(n/m) + 2 repetitions, it contains patterns incompatible with any repetition of a

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through an example where a = "abcd" and b = "cdabcdab".

Step 1: Calculate theoretical minimum repetitions

  • Length of a (m) = 4
  • Length of b (n) = 8
  • Minimum repetitions = ceil(8/4) = 2
  • Initial repeated string: "abcdabcd"

Step 2: Check if b is a substring

  • Is "cdabcdab" in "abcdabcd"? No
  • The issue: b starts with "cd" which appears at the end of the first a, but we need "cdabcdab" which requires characters from a third copy

Step 3: Add one more copy (attempt 1)

  • Repetitions = 3
  • New repeated string: "abcdabcdabcd"
  • Is "cdabcdab" in "abcdabcdabcd"? Yes!
  • Found at position 2: "ab[cdabcdab]cd"

Result: Return 3

This example demonstrates why we need extra copies beyond the theoretical minimum. Even though b has 8 characters and could theoretically fit in 2 copies of a (which gives us 8 characters), the actual substring b spans across boundaries - it starts in the middle of one copy and ends in the middle of another, requiring 3 total copies of a.

Another quick example where it's impossible:

  • a = "abc", b = "abac"
  • Minimum repetitions = ceil(4/3) = 2 → "abcabc"
  • Check: "abac" not in "abcabc" ❌
  • Add one more: "abac" not in "abcabcabc" ❌
  • Add another: "abac" not in "abcabcabcabc" ❌
  • Return -1 (impossible because 'a' never appears after 'b' in any repetition of "abc")

Solution Implementation

1from math import ceil
2
3class Solution:
4    def repeatedStringMatch(self, a: str, b: str) -> int:
5        # Get lengths of both strings
6        len_a, len_b = len(a), len(b)
7      
8        # Calculate minimum number of repetitions needed
9        # This is the theoretical minimum based on lengths
10        min_repetitions = ceil(len_b / len_a)
11      
12        # Build initial repeated string with minimum repetitions
13        repeated_string_list = [a] * min_repetitions
14      
15        # Check if b is a substring, trying up to 3 additional repetitions
16        # This handles edge cases where b might start/end in the middle of a
17        for _ in range(3):
18            # Join the list to form the repeated string and check if b is a substring
19            repeated_string = ''.join(repeated_string_list)
20            if b in repeated_string:
21                return min_repetitions
22          
23            # If not found, add one more repetition and try again
24            min_repetitions += 1
25            repeated_string_list.append(a)
26      
27        # If b is not found after trying additional repetitions, return -1
28        return -1
29
1class Solution {
2    public int repeatedStringMatch(String a, String b) {
3        int lengthA = a.length();
4        int lengthB = b.length();
5      
6        // Calculate minimum number of repetitions needed
7        // This ensures the repeated string is at least as long as b
8        int minRepetitions = (lengthB + lengthA - 1) / lengthA;
9      
10        // Build initial repeated string with minimum repetitions
11        StringBuilder repeatedString = new StringBuilder(a.repeat(minRepetitions));
12      
13        // Try up to 3 additional repetitions to find b as substring
14        // We need at most 2 extra repetitions to handle edge cases where
15        // b starts near the end of one repetition and ends in the next
16        for (int additionalAttempts = 0; additionalAttempts < 3; additionalAttempts++) {
17            // Check if current repeated string contains b
18            if (repeatedString.toString().contains(b)) {
19                return minRepetitions;
20            }
21          
22            // Add one more repetition of a and increment counter
23            minRepetitions++;
24            repeatedString.append(a);
25        }
26      
27        // If b is not found after sufficient repetitions, return -1
28        return -1;
29    }
30}
31
1class Solution {
2public:
3    int repeatedStringMatch(string a, string b) {
4        int lenA = a.size();
5        int lenB = b.size();
6      
7        // Calculate minimum number of repetitions needed
8        // This ensures the repeated string is at least as long as b
9        int minRepetitions = (lenB + lenA - 1) / lenA;
10      
11        // Build the initial repeated string with minimum repetitions
12        string repeatedStr = "";
13        for (int i = 0; i < minRepetitions; ++i) {
14            repeatedStr += a;
15        }
16      
17        // Try up to 3 additional repetitions
18        // We need at most 2 extra repetitions to handle edge cases
19        // where b starts at the end of one repetition and ends at the beginning of another
20        for (int i = 0; i < 3; ++i) {
21            // Check if b is a substring of the current repeated string
22            if (repeatedStr.find(b) != string::npos) {
23                return minRepetitions;
24            }
25          
26            // Add one more repetition and increment the count
27            minRepetitions++;
28            repeatedStr += a;
29        }
30      
31        // If b is not found after trying additional repetitions, return -1
32        return -1;
33    }
34};
35
1/**
2 * Finds the minimum number of times string 'a' must be repeated
3 * so that string 'b' becomes a substring of the repeated string.
4 * 
5 * @param a - The string to be repeated
6 * @param b - The target substring to search for
7 * @returns The minimum repetition count, or -1 if impossible
8 */
9function repeatedStringMatch(a: string, b: string): number {
10    const lengthA: number = a.length;
11    const lengthB: number = b.length;
12  
13    // Calculate minimum repetitions needed based on length ratio
14    let repetitionCount: number = Math.ceil(lengthB / lengthA);
15  
16    // Build initial repeated string
17    let repeatedString: string = a.repeat(repetitionCount);
18  
19    // Try up to 3 iterations to find substring b
20    // This accounts for cases where b might span across boundaries
21    for (let attempt = 0; attempt < 3; attempt++) {
22        // Check if current repeated string contains b
23        if (repeatedString.includes(b)) {
24            return repetitionCount;
25        }
26      
27        // Add one more repetition for next attempt
28        repetitionCount++;
29        repeatedString += a;
30    }
31  
32    // Return -1 if b cannot be found after all attempts
33    return -1;
34}
35

Time and Space Complexity

Time Complexity: O(n * (n + m))

The algorithm starts by calculating the minimum number of repetitions needed as ceil(n/m). Then it performs up to 3 additional iterations. In each iteration:

  • It joins the list t which contains ans copies of string a, creating a string of length ans * m. This join operation takes O(ans * m) time.
  • It checks if b is a substring of the joined string using the in operator, which takes O(ans * m * n) time in the worst case using a naive string matching algorithm.
  • Since ans starts at ceil(n/m) and can go up to ceil(n/m) + 2, the maximum length of the concatenated string is approximately (ceil(n/m) + 2) * m, which is O(n + m).
  • Therefore, each substring check takes O((n + m) * n) time.
  • With at most 3 iterations, the total time complexity is O(n * (n + m)).

Space Complexity: O(n + m)

  • The list t stores at most ceil(n/m) + 2 references to string a, but since Python strings are immutable and these are references to the same string object, this takes O(ceil(n/m) + 2) space for the list itself.
  • The ''.join(t) operation creates a new string of length at most (ceil(n/m) + 2) * m, which is O(n + m).
  • Therefore, the overall space complexity is O(n + m).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Checking Too Few Additional Repetitions

One common mistake is only checking the theoretical minimum repetitions without considering edge cases where b might span across boundaries of repeated a strings.

Problem Example:

  • a = "abc", b = "cabca"
  • Theoretical minimum: ceil(5/3) = 2 → gives us "abcabc"
  • But "cabca" is NOT in "abcabc"
  • We need 3 repetitions: "abcabcabc" contains "cabca"

Why This Happens: The substring b might start in the middle of one copy of a and end in the middle of another copy. The theoretical minimum only ensures we have enough characters total, not that the pattern aligns correctly.

Solution: Always check at least 2 additional repetitions beyond the theoretical minimum. The current code checks 3 total attempts, which is sufficient because:

  • If b requires more than ceil(len(b)/len(a)) + 2 repetitions
  • Then b must contain a pattern that can never appear in any repetition of a

2. Inefficient String Concatenation in the Loop

While the current solution works, repeatedly joining strings inside the loop can be inefficient for large strings.

Inefficient Pattern:

for _ in range(3):
    if b in ''.join(t):  # Joining on every iteration
        return ans
    ans += 1
    t.append(a)

Optimized Solution:

# Build the string incrementally
repeated_str = a * min_repetitions
for i in range(3):
    if b in repeated_str:
        return min_repetitions + i
    repeated_str += a

3. Not Handling the Case Where a is Longer Than b

When len(a) > len(b), the theoretical minimum is 1, but developers might incorrectly assume b must be a substring of a single a.

Problem Example:

  • a = "abcdef", b = "defabc"
  • Theoretical minimum: ceil(6/6) = 1 → gives us "abcdef"
  • But "defabc" is NOT in "abcdef"
  • We need 2 repetitions: "abcdefabcdef" contains "defabc"

Key Insight: Even when a is longer than b, we might need 2 copies of a if b is formed by the end of one a concatenated with the beginning of another a.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

How would you design a stack which has a function min that returns the minimum element in the stack, in addition to push and pop? All push, pop, min should have running time O(1).


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More