Facebook Pixel

1638. Count Substrings That Differ by One Character

MediumHash TableStringDynamic ProgrammingEnumeration
Leetcode Link

Problem Description

You are given two strings s and t. Your task is to count how many ways you can:

  1. Choose a non-empty substring from string s
  2. Change exactly one character in that substring
  3. Make the resulting substring match some substring in string t

The key requirement is that the substring from s and the substring from t must differ by exactly one character - no more, no less.

For example, if s = "computer" and t = "computation", one valid way would be:

  • Take substring "compute" from s (positions 0-6)
  • Change the 'e' to 'a' to get "computa"
  • This matches the substring "computa" in t (positions 0-6)

The solution approach works by:

  1. Iterating through each position i in string s and position j in string t
  2. When characters at these positions differ (s[i] != t[j]), treating this as the single differing character
  3. Extending left to count how many matching characters exist before this position
  4. Extending right to count how many matching characters exist after this position
  5. The total valid substrings with this differing position is (l + 1) * (r + 1), where l is the matching length on the left and r is the matching length on the right

The algorithm counts all possible substrings that can be formed by choosing different starting and ending positions around each valid differing character position.

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

Intuition

The key insight is that for two substrings to differ by exactly one character, there must be a single "mismatch point" where the characters are different, with all other characters matching perfectly.

Think of it this way: if we fix a mismatch point at positions (i, j) where s[i] != t[j], then any valid substring pair must:

  • Include this mismatch point
  • Have all other characters matching

So the natural question becomes: given a fixed mismatch point, how many valid substring pairs can we form around it?

This leads us to consider:

  1. How far can we extend to the left while characters still match?
  2. How far can we extend to the right while characters still match?

If we can extend l positions to the left and r positions to the right, then we have choices:

  • We can start our substring anywhere from i-l to i (that's l+1 choices)
  • We can end our substring anywhere from i to i+r (that's r+1 choices)

Since these choices are independent, the total number of valid substrings centered at this mismatch point is (l+1) * (r+1).

By iterating through all possible mismatch points (i, j) where s[i] != t[j] and calculating the number of valid substrings for each, we get the total count. This approach ensures we count each valid substring pair exactly once, as each pair has exactly one mismatch point that uniquely identifies it.

Learn more about Dynamic Programming patterns.

Solution Approach

The implementation uses a nested loop approach to examine all possible mismatch points between strings s and t.

Main Algorithm Steps:

  1. Iterate through all possible mismatch points:

    • Use two nested loops with indices i for string s and j for string t
    • Check each pair of positions (i, j) to find where s[i] != t[j]
  2. For each mismatch point found:

    • Initialize two counters: l for left extension and r for right extension
  3. Extend left from the mismatch point:

    while i > l and j > l and s[i - l - 1] == t[j - l - 1]:
        l += 1
    • Keep moving left while characters match
    • Stop when we reach the beginning of either string or find a mismatch
    • l represents how many matching positions exist to the left
  4. Extend right from the mismatch point:

    while i + r + 1 < m and j + r + 1 < n and s[i + r + 1] == t[j + r + 1]:
        r += 1
    • Keep moving right while characters match
    • Stop when we reach the end of either string or find a mismatch
    • r represents how many matching positions exist to the right
  5. Count valid substrings for this mismatch point:

    • Calculate (l + 1) * (r + 1)
    • This represents all possible substring combinations that include the mismatch point
    • Add this count to the running total ans

Time Complexity: O(m * n * min(m, n)) where m = len(s) and n = len(t). The nested loops give us O(m * n), and for each mismatch point, we might extend up to min(m, n) positions.

Space Complexity: O(1) as we only use a constant amount of extra space for variables.

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 a small example with s = "abc" and t = "bbc".

We need to find all ways to change exactly one character in a substring of s to match a substring in t.

Step 1: Check position (i=0, j=0)

  • s[0] = 'a' and t[0] = 'b' → They differ! This is a potential mismatch point.
  • Extend left: Can't extend (already at the beginning), so l = 0
  • Extend right: Check if s[1] = 'b' equals t[1] = 'b' → Yes! r = 1
    • Check if s[2] = 'c' equals t[2] = 'c' → Yes! r = 2
    • Can't extend further (reached end), so r = 2
  • Valid substrings: (0 + 1) * (2 + 1) = 3
    • These are: "a"→"b", "ab"→"bb", "abc"→"bbc"

Step 2: Check position (i=0, j=1)

  • s[0] = 'a' and t[1] = 'b' → They differ!
  • Extend left: Can't extend (j would go negative), so l = 0
  • Extend right: Check if s[1] = 'b' equals t[2] = 'c' → No! r = 0
  • Valid substrings: (0 + 1) * (0 + 1) = 1
    • This is: "a"→"b"

Step 3: Check position (i=0, j=2)

  • s[0] = 'a' and t[2] = 'c' → They differ!
  • Extend left: Can't extend, so l = 0
  • Extend right: Check if s[1] = 'b' would equal t[3] → Out of bounds! r = 0
  • Valid substrings: (0 + 1) * (0 + 1) = 1
    • This is: "a"→"c"

Step 4: Check position (i=1, j=0)

  • s[1] = 'b' and t[0] = 'b' → They match! Skip this pair.

Step 5: Check position (i=1, j=1)

  • s[1] = 'b' and t[1] = 'b' → They match! Skip.

Step 6: Check position (i=1, j=2)

  • s[1] = 'b' and t[2] = 'c' → They differ!
  • Extend left: Check if s[0] = 'a' equals t[1] = 'b' → No! l = 0
  • Extend right: Check if s[2] = 'c' would equal t[3] → Out of bounds! r = 0
  • Valid substrings: (0 + 1) * (0 + 1) = 1
    • This is: "b"→"c"

Step 7: Check position (i=2, j=0)

  • s[2] = 'c' and t[0] = 'b' → They differ!
  • Extend left: Check if s[1] = 'b' would equal t[-1] → Out of bounds! l = 0
  • Extend right: Check if s[3] would exist → Out of bounds! r = 0
  • Valid substrings: (0 + 1) * (0 + 1) = 1
    • This is: "c"→"b"

Step 8: Check positions (i=2, j=1) and (i=2, j=2)

  • s[2] = 'c' and t[1] = 'b' → They differ! Count = 1 (substring "c"→"b")
  • s[2] = 'c' and t[2] = 'c' → They match! Skip.

Total count: 3 + 1 + 1 + 1 + 1 + 1 = 8

The algorithm found 8 different ways to change exactly one character in a substring of s to match a substring in t.

Solution Implementation

1class Solution:
2    def countSubstrings(self, s: str, t: str) -> int:
3        """
4        Count substrings from s and t that differ by exactly one character.
5      
6        Args:
7            s: First string
8            t: Second string
9          
10        Returns:
11            Number of substring pairs that differ by exactly one character
12        """
13        total_count = 0
14        len_s, len_t = len(s), len(t)
15      
16        # Iterate through all character pairs from both strings
17        for i in range(len_s):
18            for j in range(len_t):
19                # Check if characters at current positions differ
20                if s[i] != t[j]:
21                    # Count matching characters to the left of mismatch
22                    left_matches = 0
23                    while (i - left_matches - 1 >= 0 and 
24                           j - left_matches - 1 >= 0 and 
25                           s[i - left_matches - 1] == t[j - left_matches - 1]):
26                        left_matches += 1
27                  
28                    # Count matching characters to the right of mismatch
29                    right_matches = 0
30                    while (i + right_matches + 1 < len_s and 
31                           j + right_matches + 1 < len_t and 
32                           s[i + right_matches + 1] == t[j + right_matches + 1]):
33                        right_matches += 1
34                  
35                    # Calculate total substrings with this mismatch point
36                    # (left_matches + 1) possible starting positions
37                    # (right_matches + 1) possible ending positions
38                    total_count += (left_matches + 1) * (right_matches + 1)
39      
40        return total_count
41
1class Solution {
2    public int countSubstrings(String s, String t) {
3        int totalCount = 0;
4        int sLength = s.length();
5        int tLength = t.length();
6      
7        // Iterate through each position in string s
8        for (int sIndex = 0; sIndex < sLength; ++sIndex) {
9            // Iterate through each position in string t
10            for (int tIndex = 0; tIndex < tLength; ++tIndex) {
11                // Check if characters at current positions are different
12                if (s.charAt(sIndex) != t.charAt(tIndex)) {
13                    // Count matching characters to the left of the mismatch
14                    int leftMatchCount = 0;
15                    while (sIndex - leftMatchCount > 0 && 
16                           tIndex - leftMatchCount > 0 && 
17                           s.charAt(sIndex - leftMatchCount - 1) == t.charAt(tIndex - leftMatchCount - 1)) {
18                        ++leftMatchCount;
19                    }
20                  
21                    // Count matching characters to the right of the mismatch
22                    int rightMatchCount = 0;
23                    while (sIndex + rightMatchCount + 1 < sLength && 
24                           tIndex + rightMatchCount + 1 < tLength &&
25                           s.charAt(sIndex + rightMatchCount + 1) == t.charAt(tIndex + rightMatchCount + 1)) {
26                        ++rightMatchCount;
27                    }
28                  
29                    // Calculate total substrings with exactly one mismatch at this position
30                    // (leftMatchCount + 1) represents possible starting positions
31                    // (rightMatchCount + 1) represents possible ending positions
32                    totalCount += (leftMatchCount + 1) * (rightMatchCount + 1);
33                }
34            }
35        }
36      
37        return totalCount;
38    }
39}
40
1class Solution {
2public:
3    int countSubstrings(string s, string t) {
4        int result = 0;
5        int sLength = s.size();
6        int tLength = t.size();
7      
8        // Iterate through each character position in string s
9        for (int i = 0; i < sLength; ++i) {
10            // Iterate through each character position in string t
11            for (int j = 0; j < tLength; ++j) {
12                // Find positions where characters differ (mismatch point)
13                if (s[i] != t[j]) {
14                    // Count matching characters to the left of mismatch
15                    int leftMatchCount = 0;
16                    while (i - leftMatchCount > 0 && 
17                           j - leftMatchCount > 0 && 
18                           s[i - leftMatchCount - 1] == t[j - leftMatchCount - 1]) {
19                        ++leftMatchCount;
20                    }
21                  
22                    // Count matching characters to the right of mismatch
23                    int rightMatchCount = 0;
24                    while (i + rightMatchCount + 1 < sLength && 
25                           j + rightMatchCount + 1 < tLength && 
26                           s[i + rightMatchCount + 1] == t[j + rightMatchCount + 1]) {
27                        ++rightMatchCount;
28                    }
29                  
30                    // Calculate total substrings with exactly one difference at position (i, j)
31                    // (leftMatchCount + 1) represents possible starting positions
32                    // (rightMatchCount + 1) represents possible ending positions
33                    result += (leftMatchCount + 1) * (rightMatchCount + 1);
34                }
35            }
36        }
37      
38        return result;
39    }
40};
41
1function countSubstrings(s: string, t: string): number {
2    let result = 0;
3    const sLength = s.length;
4    const tLength = t.length;
5  
6    // Iterate through each character position in string s
7    for (let i = 0; i < sLength; i++) {
8        // Iterate through each character position in string t
9        for (let j = 0; j < tLength; j++) {
10            // Find positions where characters differ (mismatch point)
11            if (s[i] !== t[j]) {
12                // Count matching characters to the left of mismatch
13                let leftMatchCount = 0;
14                while (i - leftMatchCount > 0 && 
15                       j - leftMatchCount > 0 && 
16                       s[i - leftMatchCount - 1] === t[j - leftMatchCount - 1]) {
17                    leftMatchCount++;
18                }
19              
20                // Count matching characters to the right of mismatch
21                let rightMatchCount = 0;
22                while (i + rightMatchCount + 1 < sLength && 
23                       j + rightMatchCount + 1 < tLength && 
24                       s[i + rightMatchCount + 1] === t[j + rightMatchCount + 1]) {
25                    rightMatchCount++;
26                }
27              
28                // Calculate total substrings with exactly one difference at position (i, j)
29                // (leftMatchCount + 1) represents possible starting positions
30                // (rightMatchCount + 1) represents possible ending positions
31                result += (leftMatchCount + 1) * (rightMatchCount + 1);
32            }
33        }
34    }
35  
36    return result;
37}
38

Time and Space Complexity

Time Complexity: O(m * n * min(m, n)) where m = len(s) and n = len(t)

The algorithm uses nested loops to iterate through all pairs of indices (i, j) where i is from string s and j is from string t. This gives us O(m * n) iterations. For each pair where s[i] != t[j], the algorithm extends left and right to count matching characters. In the worst case, these extensions can go up to min(m, n) characters (limited by the shorter string length). Therefore, the overall time complexity is O(m * n * min(m, n)).

Space Complexity: O(1)

The algorithm only uses a constant amount of extra space for variables ans, m, n, i, j, a, b, l, and r. No additional data structures that scale with input size are created, making the space complexity constant.

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

Common Pitfalls

1. Attempting to Use Set or Hash-based Approaches

A common mistake is trying to generate all substrings from both strings and compare them using sets or hash tables. This approach fails because:

  • Memory explosion: Storing all possible substrings requires O(m²) + O(n²) space
  • Incorrect counting: Simply checking if substrings differ by one character doesn't handle the counting correctly
  • Performance issues: Generating and comparing all substring pairs is inefficient

Incorrect approach example:

# DON'T DO THIS - generates all substrings first
def countSubstrings_wrong(s, t):
    s_substrings = set()
    for i in range(len(s)):
        for j in range(i+1, len(s)+1):
            s_substrings.add(s[i:j])
    # Then try to match with t substrings...

2. Double Counting or Missing Valid Substrings

When implementing the extension logic, it's easy to:

  • Count the same substring pair multiple times
  • Miss valid substrings by incorrect boundary checks

Key insight: The algorithm avoids double counting by treating each (i, j) mismatch position uniquely. Each substring pair is counted exactly once because it has exactly one mismatch position.

3. Incorrect Boundary Conditions in Extension Logic

The while loops for extending left and right must check multiple conditions simultaneously:

Wrong implementation:

# Missing proper boundary checks
while s[i - l - 1] == t[j - l - 1]:  # IndexError!
    l += 1

Correct implementation:

# All boundary conditions must be checked
while (i > l and j > l and 
       s[i - l - 1] == t[j - l - 1]):
    l += 1

4. Misunderstanding the Multiplication Formula

The formula (l + 1) * (r + 1) might seem arbitrary but represents:

  • (l + 1): Number of possible starting positions (including position i-l)
  • (r + 1): Number of possible ending positions (including position i+r)

Each combination of start and end position that includes the mismatch point at (i, j) creates a valid substring pair.

5. Handling Edge Cases Incorrectly

Special cases to consider:

  • Single character strings
  • Strings with no common characters
  • Strings where one is much longer than the other

The algorithm naturally handles these because:

  • If no mismatches exist between positions, nothing is counted
  • Extension stops at string boundaries automatically
  • The nested loops ensure all valid position pairs are checked

Solution verification tip: Test with simple examples like s="a", t="b" (should return 1) and s="ab", t="bb" (should return 3) to ensure your implementation handles basic cases correctly.

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

How does merge sort divide the problem into subproblems?


Recommended Readings

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

Load More