Facebook Pixel

1794. Count Pairs of Equal Substrings With Minimum Difference

MediumGreedyHash TableString
Leetcode Link

Problem Description

You are given two strings firstString and secondString that consist only of lowercase English letters. You need to find and count special quadruples (i, j, a, b) where:

  1. i and j are indices in firstString where 0 <= i <= j < firstString.length
  2. a and b are indices in secondString where 0 <= a <= b < secondString.length
  3. The substring firstString[i...j] equals the substring secondString[a...b]
  4. The value j - a is the minimum possible among all valid quadruples

The key insight is that for any matching substring, the difference j - a represents how far apart the ending position in firstString is from the starting position in secondString. The problem asks for all quadruples that achieve this minimum difference.

For example, if firstString = "abcd" and secondString = "bccda", and we find that substring "cd" appears in both strings, we would check different positions where this occurs and calculate j - a for each valid match. We want to count only those quadruples where this difference is minimized.

Since substrings must be equal, their lengths must match, meaning j - i = b - a. This can be rearranged to j - a = b - i. The problem essentially asks us to minimize this cross-difference between the ending index in the first string and the starting index in the second string.

The solution cleverly recognizes that to minimize j - a, we need to find characters that appear in both strings, where the character appears as early as possible in firstString (small i) and as late as possible in secondString (large index). For single-character substrings (where i = j and a = b), this simplifies to finding matching characters and minimizing i - a.

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

Intuition

Let's think about what the problem is really asking. We need matching substrings from both strings, and among all such matches, we want those where j - a is minimized.

Since the substrings must be equal, they must have the same length. This means j - i = b - a, which we can rearrange to get j - a = b - i. So minimizing j - a is equivalent to minimizing b - i.

Now here's the key observation: instead of checking all possible substrings (which would be expensive), let's start with the simplest case - single character substrings where i = j and a = b. In this case, we're looking for matching characters between the two strings, and we want to minimize i - a.

For a single character match, i - a represents the difference between where the character appears in firstString and where it appears in secondString. To minimize this:

  • We want the character to appear as early as possible in firstString (small i)
  • We want the character to appear as late as possible in secondString (large a)

This leads us to a greedy strategy: for each character in secondString, we only need to remember its last occurrence position. Then, as we traverse firstString from left to right, for each character we encounter, we check if it exists in secondString. If it does, we calculate i - last[c] where last[c] is the last position of character c in secondString.

Why does this work for single characters only? Because for longer substrings, the complexity increases significantly, but the problem's optimal solution always comes from single-character matches. This is because extending a substring in both strings increases both j and b by the same amount, keeping j - a unchanged, but we'd need the extended parts to match exactly, which is a stricter condition without improving our objective.

Therefore, we only need to:

  1. Track the last occurrence of each character in secondString
  2. For each character in firstString, check if it exists in secondString and calculate the difference
  3. Keep track of the minimum difference and count how many times we achieve it

Learn more about Greedy patterns.

Solution Approach

Based on our intuition, we implement a greedy solution using a hash table to efficiently find the minimum i - a value for matching characters.

Step 1: Build the hash table for last occurrences

We create a dictionary last that stores the last occurrence index of each character in secondString:

last = {c: i for i, c in enumerate(secondString)}

This iterates through secondString and updates the dictionary with each character's position. Since we're updating as we go, earlier occurrences get overwritten by later ones, giving us the last position of each character.

Step 2: Traverse firstString and find minimum difference

We initialize two variables:

  • ans = 0: counts the number of quadruples with minimum j - a
  • mi = inf: tracks the minimum value of i - last[c] found so far

Then we iterate through firstString with both index i and character c:

for i, c in enumerate(firstString):
    if c in last:
        t = i - last[c]

For each character that exists in both strings, we calculate the difference t = i - last[c].

Step 3: Update the minimum and count

We compare the current difference t with our minimum mi:

if mi > t:
    mi = t
    ans = 1
elif mi == t:
    ans += 1
  • If t is smaller than the current minimum, we've found a new minimum. We update mi to t and reset the count ans to 1.
  • If t equals the current minimum, we've found another quadruple with the same minimum difference, so we increment ans.

Step 4: Return the result

After processing all characters in firstString, ans contains the total count of quadruples that achieve the minimum j - a value.

The time complexity is O(n + m) where n and m are the lengths of the two strings - we traverse each string once. The space complexity is O(min(m, 26)) for the hash table, which stores at most 26 entries (one for each lowercase letter).

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's walk through the solution with firstString = "abcd" and secondString = "bccda".

Step 1: Build the hash table for last occurrences in secondString

We iterate through secondString = "bccda" and record the last position of each character:

  • Index 0: 'b' → last['b'] = 0
  • Index 1: 'c' → last['c'] = 1
  • Index 2: 'c' → last['c'] = 2 (updates previous value)
  • Index 3: 'd' → last['d'] = 3
  • Index 4: 'a' → last['a'] = 4

Final hash table: last = {'b': 0, 'c': 2, 'd': 3, 'a': 4}

Step 2: Traverse firstString and calculate differences

Initialize mi = infinity and ans = 0.

Now iterate through firstString = "abcd":

  • i = 0, c = 'a':

    • 'a' exists in last with last['a'] = 4
    • Calculate t = 0 - 4 = -4
    • Since -4 < infinity, update mi = -4 and ans = 1
    • This represents the quadruple (0, 0, 4, 4) with substring "a"
  • i = 1, c = 'b':

    • 'b' exists in last with last['b'] = 0
    • Calculate t = 1 - 0 = 1
    • Since 1 > -4, no update needed
    • This would represent quadruple (1, 1, 0, 0) with substring "b"
  • i = 2, c = 'c':

    • 'c' exists in last with last['c'] = 2
    • Calculate t = 2 - 2 = 0
    • Since 0 > -4, no update needed
    • This would represent quadruple (2, 2, 2, 2) with substring "c"
  • i = 3, c = 'd':

    • 'd' exists in last with last['d'] = 3
    • Calculate t = 3 - 3 = 0
    • Since 0 > -4, no update needed
    • This would represent quadruple (3, 3, 3, 3) with substring "d"

Step 3: Return result

The minimum difference found is mi = -4, achieved by exactly 1 quadruple.

Therefore, the answer is 1.

The quadruple (0, 0, 4, 4) represents:

  • Substring from firstString[0...0] = "a"
  • Substring from secondString[4...4] = "a"
  • The value j - a = 0 - 4 = -4 is the minimum possible

Solution Implementation

1from math import inf
2
3class Solution:
4    def countQuadruples(self, firstString: str, secondString: str) -> int:
5        # Build a dictionary mapping each character to its last occurrence index in secondString
6        last_occurrence = {char: index for index, char in enumerate(secondString)}
7
8        # Initialize count of valid quadruples and minimum difference
9        count = 0
10        min_difference = inf
11
12        # Iterate through each character and its index in firstString
13        for i, char in enumerate(firstString):
14            # Check if this character exists in secondString
15            if char in last_occurrence:
16                # Calculate the difference: index in firstString - last index in secondString
17                difference = i - last_occurrence[char]
18
19                # If we found a smaller difference, reset count to 1
20                if min_difference > difference:
21                    min_difference = difference
22                    count = 1
23                # If the difference equals the minimum, increment count
24                elif min_difference == difference:
25                    count += 1
26
27        return count
28
1class Solution {
2    public int countQuadruples(String firstString, String secondString) {
3        // Array to store the last occurrence index (1-based) of each character in secondString
4        // Index represents character ('a' = 0, 'b' = 1, ..., 'z' = 25)
5        int[] lastOccurrence = new int[26];
6
7        // Populate the last occurrence of each character in secondString
8        // Using 1-based indexing (position + 1) to distinguish from uninitialized values (0)
9        for (int i = 0; i < secondString.length(); i++) {
10            lastOccurrence[secondString.charAt(i) - 'a'] = i + 1;
11        }
12
13        // Initialize result counter and minimum difference value
14        int count = 0;
15        int minDifference = Integer.MAX_VALUE;
16
17        // Iterate through each character in firstString
18        for (int i = 0; i < firstString.length(); i++) {
19            // Get the last occurrence position of current character in secondString
20            int lastPositionInSecond = lastOccurrence[firstString.charAt(i) - 'a'];
21
22            // If this character exists in secondString (position > 0)
23            if (lastPositionInSecond > 0) {
24                // Calculate the difference: i - j (adjusting for 1-based indexing)
25                int difference = i - lastPositionInSecond;
26
27                // If we found a smaller difference, update minimum and reset count
28                if (minDifference > difference) {
29                    minDifference = difference;
30                    count = 1;
31                }
32                // If difference equals current minimum, increment count
33                else if (minDifference == difference) {
34                    count++;
35                }
36            }
37        }
38
39        return count;
40    }
41}
42
1class Solution {
2public:
3    int countQuadruples(string firstString, string secondString) {
4        // Array to store the last occurrence index (1-indexed) of each character in secondString
5        // Index represents character ('a' = 0, 'b' = 1, ..., 'z' = 25)
6        int lastOccurrence[26] = {0};
7
8        // Record the last position (1-indexed) of each character in secondString
9        for (int i = 0; i < secondString.size(); ++i) {
10            lastOccurrence[secondString[i] - 'a'] = i + 1;
11        }
12
13        // Initialize result counter and minimum difference value
14        int count = 0;
15        int minDifference = 1 << 30;  // Large initial value (2^30)
16
17        // Iterate through each character in firstString
18        for (int i = 0; i < firstString.size(); ++i) {
19            // Get the last occurrence position of current character in secondString
20            int lastPosInSecond = lastOccurrence[firstString[i] - 'a'];
21
22            // If this character exists in secondString
23            if (lastPosInSecond > 0) {
24                // Calculate the difference: firstString index - secondString index
25                // Note: i is 0-indexed, lastPosInSecond is 1-indexed, so difference = i - (lastPosInSecond - 1) = i - lastPosInSecond + 1
26                // But the code uses i - lastPosInSecond directly for comparison
27                int difference = i - lastPosInSecond;
28
29                // If we found a smaller difference, update minimum and reset count
30                if (minDifference > difference) {
31                    minDifference = difference;
32                    count = 1;
33                }
34                // If difference equals current minimum, increment count
35                else if (minDifference == difference) {
36                    ++count;
37                }
38            }
39        }
40
41        return count;
42    }
43};
44
1/**
2 * Counts the number of quadruples (i, j, a, b) where:
3 * - firstString[i] == a, secondString[j] == b, and a == b
4 * - The value (i - j) is minimized
5 *
6 * @param firstString - The first input string
7 * @param secondString - The second input string
8 * @returns The count of quadruples with minimum (i - j) value
9 */
10function countQuadruples(firstString: string, secondString: string): number {
11    // Array to store the last occurrence index (1-based) of each letter in secondString
12    // Index represents letter (0 for 'a', 1 for 'b', ..., 25 for 'z')
13    const lastOccurrenceInSecond: number[] = new Array(26).fill(0);
14
15    // Populate the last occurrence of each character in secondString
16    // Using 1-based indexing (i + 1) to distinguish between "not found" (0) and index 0
17    for (let i = 0; i < secondString.length; ++i) {
18        const charIndex: number = secondString.charCodeAt(i) - 97; // Convert char to 0-25 range
19        lastOccurrenceInSecond[charIndex] = i + 1;
20    }
21
22    // Track the answer count and minimum difference found
23    let quadrupleCount: number = 0;
24    let minimumDifference: number = Infinity;
25
26    // Iterate through each character in firstString
27    for (let i = 0; i < firstString.length; ++i) {
28        const charIndex: number = firstString.charCodeAt(i) - 97; // Convert char to 0-25 range
29        const lastPositionInSecond: number = lastOccurrenceInSecond[charIndex];
30
31        // Check if this character exists in secondString
32        if (lastPositionInSecond > 0) {
33            // Calculate the difference (i - j), adjusting for 1-based indexing
34            const currentDifference: number = i - lastPositionInSecond;
35
36            if (minimumDifference > currentDifference) {
37                // Found a new minimum difference
38                minimumDifference = currentDifference;
39                quadrupleCount = 1;
40            } else if (minimumDifference === currentDifference) {
41                // Found another quadruple with the same minimum difference
42                ++quadrupleCount;
43            }
44        }
45    }
46
47    return quadrupleCount;
48}
49

Time and Space Complexity

The time complexity is O(m + n), where m is the length of firstString and n is the length of secondString. This is because:

  • Building the last dictionary requires iterating through secondString once, which takes O(n) time
  • The main loop iterates through firstString once, which takes O(m) time
  • Dictionary lookup operation c in last and accessing last[c] are O(1) operations
  • Therefore, the total time complexity is O(n) + O(m) = O(m + n)

The space complexity is O(C), where C is the size of the character set. In this problem, since we're dealing with lowercase English letters, C = 26. This is because:

  • The last dictionary stores at most one entry for each unique character in secondString
  • In the worst case, secondString contains all 26 lowercase letters, so the dictionary would have at most 26 key-value pairs
  • The other variables (ans, mi, i, c, t) use constant space O(1)
  • Therefore, the total space complexity is O(C) = O(26) = O(1) (constant space)

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

Common Pitfalls

Pitfall 1: Misunderstanding the Problem Constraints

The Issue: A common misunderstanding is thinking we need to find all possible matching substrings of any length, not just single characters. The solution only considers single-character matches (where i = j and a = b), which might seem incorrect at first glance.

Why This Works: When we have the constraint that j - i = b - a (equal length substrings) and want to minimize j - a, we can rewrite this as minimizing b - i. For any multi-character substring match, the minimum j - a would require the substring to start as early as possible in firstString and as late as possible in secondString. However, the mathematical analysis shows that single-character matches are sufficient to find the global minimum.

Solution: Trust the mathematical reduction. The problem simplifies to finding matching single characters because:

  • For longer substrings, the constraints become more restrictive
  • Single character matches give us the most flexibility in positioning
  • The minimum difference will always be achieved by some single-character match

Pitfall 2: Using First Occurrence Instead of Last Occurrence

The Issue: Developers might intuitively store the first occurrence of each character in secondString:

# Incorrect approach
first_occurrence = {}
for i, char in enumerate(secondString):
    if char not in first_occurrence:
        first_occurrence[char] = i

Why This is Wrong: To minimize j - a (or i - a for single characters), we need a to be as large as possible. Using the first occurrence would give us the smallest a, which maximizes rather than minimizes the difference.

Solution: Always use the last occurrence:

# Correct approach
last_occurrence = {char: index for index, char in enumerate(secondString)}

Pitfall 3: Initializing the Minimum Incorrectly

The Issue: Initializing min_difference to 0 or a small positive number:

# Incorrect
min_difference = 0  # or min_difference = 1

Why This is Wrong: The difference i - last[char] can be negative when a character appears later in secondString than in firstString. If we initialize to 0, we might miss valid negative differences that are actually the minimum.

Solution: Initialize to positive infinity to ensure any valid difference will be smaller:

min_difference = inf

Pitfall 4: Not Handling Missing Characters

The Issue: Forgetting to check if a character from firstString exists in secondString:

# Incorrect - will raise KeyError
for i, char in enumerate(firstString):
    difference = i - last_occurrence[char]  # KeyError if char not in last_occurrence

Solution: Always check for existence before accessing:

for i, char in enumerate(firstString):
    if char in last_occurrence:
        difference = i - last_occurrence[char]
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Problem: Given a list of tasks and a list of requirements, compute a sequence of tasks that can be performed, such that we complete every task once while satisfying all the requirements.

Which of the following method should we use to solve this problem?


Recommended Readings

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

Load More