Facebook Pixel

3297. Count Substrings That Can Be Rearranged to Contain a String I

MediumHash TableStringSliding Window
Leetcode Link

Problem Description

You are given two strings word1 and word2.

A string x is considered valid if it can be rearranged (its characters can be reordered) to have word2 as a prefix. In other words, after rearranging the characters of x, the resulting string should start with word2.

Your task is to find and return the total number of valid substrings of word1. A substring is a contiguous sequence of characters within word1.

For example, if word1 = "abcabc" and word2 = "abc", then substrings like "abc", "bca", "cab", "abca", "bcab", "cabc", and "abcabc" are all valid because they contain at least the same characters as word2 with the required frequencies, allowing them to be rearranged to have "abc" as a prefix.

The key insight is that for a substring to be valid, it must contain at least all the characters of word2 with their respective frequencies. Any additional characters in the substring can be placed after the rearranged word2 prefix.

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

Intuition

The key observation is that for a substring to be rearrangeable with word2 as a prefix, it must contain at least all characters from word2 with their required frequencies. Any extra characters can simply be placed after the prefix.

This transforms the problem into finding all substrings of word1 that contain at least the character frequencies of word2. This is a classic sliding window problem pattern.

Consider how we can efficiently count valid substrings. If we fix a right endpoint r and find the minimum left endpoint l such that the substring [l, r] contains all required characters, then all substrings ending at r with starting positions from 0 to l-1 are also valid (since they contain even more characters). This gives us l valid substrings for this particular right endpoint.

The sliding window technique naturally fits here:

  1. Expand the window by moving the right pointer and adding characters
  2. When the window contains all required characters from word2, we have a valid window
  3. Shrink from the left to find the minimum valid window ending at the current right position
  4. Count all valid substrings ending at the current right position

The trick is maintaining two frequency counters - one for word2's requirements (cnt) and one for our current window (win). We use a need variable to track how many unique characters still need to reach their required frequency. When need becomes 0, we know our window is valid.

For each position, we shrink the left boundary until the window becomes invalid (missing required characters), then add l to our answer since positions [0, l-1] all form valid substrings when paired with the current right endpoint.

Learn more about Sliding Window patterns.

Solution Approach

We implement a sliding window algorithm with two pointers to efficiently count valid substrings.

Initial Setup:

  • First check if len(word1) < len(word2) - if true, return 0 immediately since no valid substring can exist
  • Create a frequency counter cnt for word2 using Counter(word2) to track required character frequencies
  • Initialize need = len(cnt) to track how many unique characters still need to reach their required frequency
  • Initialize ans = 0 for the answer, l = 0 for the left pointer, and win = Counter() for the current window's character frequencies

Main Algorithm: For each character c at position r in word1:

  1. Expand the window: Add character c to the window by incrementing win[c] += 1

  2. Check if requirement met: If win[c] == cnt[c], it means character c now meets its required frequency, so decrement need -= 1

  3. Handle valid windows: When need == 0, the current window [l, r] contains all required characters:

    • Shrink from the left to find the minimum valid window
    • While shrinking:
      • If win[word1[l]] == cnt[word1[l]], removing this character will break the validity, so increment need += 1
      • Decrement win[word1[l]] -= 1 to remove the leftmost character from the window
      • Move left pointer forward: l += 1
    • The loop exits when the window becomes invalid (need > 0)
  4. Count valid substrings: After shrinking, add l to the answer. This represents all substrings ending at position r with starting positions from 0 to l-1 (inclusive), since they all contain the required characters

Example Walkthrough: If word1 = "aaacb" and word2 = "abc":

  • When we reach position 4 (the 'b'), the window [2, 4] = "acb" contains all required characters
  • After shrinking, l = 3, meaning substrings [0,4], [1,4], and [2,4] are all valid
  • We add 3 to our answer

The time complexity is O(n) where n = len(word1), as each character is visited at most twice (once by right pointer, once by left pointer). The space complexity is O(1) since we use fixed-size counters (at most 26 characters for lowercase English letters).

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 concrete example with word1 = "bcca" and word2 = "abc".

Initial Setup:

  • cnt = {'a': 1, 'b': 1, 'c': 1} (frequency requirements from word2)
  • need = 3 (we need to satisfy 3 unique character requirements)
  • win = {} (empty window counter)
  • l = 0, ans = 0

Step-by-step execution:

r = 0, c = 'b':

  • Add 'b' to window: win = {'b': 1}
  • Check: win['b'] == cnt['b'] (1 == 1), so need = 2
  • Since need > 0, window is not valid yet
  • No substrings to count

r = 1, c = 'c':

  • Add 'c' to window: win = {'b': 1, 'c': 1}
  • Check: win['c'] == cnt['c'] (1 == 1), so need = 1
  • Since need > 0, window is not valid yet
  • No substrings to count

r = 2, c = 'c':

  • Add 'c' to window: win = {'b': 1, 'c': 2}
  • Check: win['c'] != cnt['c'] (2 != 1), so need stays 1
  • Since need > 0, window is not valid yet
  • No substrings to count

r = 3, c = 'a':

  • Add 'a' to window: win = {'b': 1, 'c': 2, 'a': 1}
  • Check: win['a'] == cnt['a'] (1 == 1), so need = 0
  • Window is valid! Current window is [0, 3] = "bcca"
  • Now shrink from left:
    • At l = 0: character 'b' has win['b'] = 1 = cnt['b'], removing it breaks validity
    • Set need = 1, decrement win['b'] = 0, move l = 1
    • Exit shrinking loop since need > 0
  • Add l = 1 to answer: ans = 1
  • This counts the substring [0, 3] = "bcca" (which can be rearranged to "abcc")

Final answer: 1

The only valid substring is "bcca" itself, which contains exactly the characters needed ('a', 'b', 'c') to form "abc" as a prefix after rearrangement.

Solution Implementation

1from collections import Counter
2
3class Solution:
4    def validSubstringCount(self, word1: str, word2: str) -> int:
5        # If word1 is shorter than word2, no valid substrings exist
6        if len(word1) < len(word2):
7            return 0
8      
9        # Count frequency of each character in word2 (target frequencies)
10        target_freq = Counter(word2)
11      
12        # Number of unique characters we need to match
13        chars_needed = len(target_freq)
14      
15        # Initialize result and left pointer for sliding window
16        result = 0
17        left = 0
18      
19        # Current window's character frequencies
20        window_freq = Counter()
21      
22        # Iterate through word1 with right pointer
23        for right, char in enumerate(word1):
24            # Add current character to window
25            window_freq[char] += 1
26          
27            # If this character now matches target frequency, decrease chars_needed
28            if window_freq[char] == target_freq[char]:
29                chars_needed -= 1
30          
31            # When window contains all required characters with sufficient frequency
32            while chars_needed == 0:
33                # Check if removing left character would break the valid condition
34                if window_freq[word1[left]] == target_freq[word1[left]]:
35                    chars_needed += 1
36              
37                # Remove leftmost character from window
38                window_freq[word1[left]] -= 1
39                left += 1
40          
41            # All substrings starting from indices 0 to left-1 and ending at right are valid
42            result += left
43      
44        return result
45
1class Solution {
2    public long validSubstringCount(String word1, String word2) {
3        // If word1 is shorter than word2, no valid substring is possible
4        if (word1.length() < word2.length()) {
5            return 0;
6        }
7      
8        // Count frequency of each character in word2
9        int[] targetFrequency = new int[26];
10        int uniqueCharsNeeded = 0;
11      
12        // Build frequency map for word2 and count unique characters
13        for (int i = 0; i < word2.length(); i++) {
14            int charIndex = word2.charAt(i) - 'a';
15            if (++targetFrequency[charIndex] == 1) {
16                // Found a new unique character in word2
17                uniqueCharsNeeded++;
18            }
19        }
20      
21        long totalValidSubstrings = 0;
22        int[] windowFrequency = new int[26];
23      
24        // Use sliding window with two pointers
25        for (int left = 0, right = 0; right < word1.length(); right++) {
26            // Expand window by including character at right pointer
27            int rightCharIndex = word1.charAt(right) - 'a';
28            if (++windowFrequency[rightCharIndex] == targetFrequency[rightCharIndex]) {
29                // This character now meets the required frequency
30                uniqueCharsNeeded--;
31            }
32          
33            // Contract window from left while maintaining validity
34            while (uniqueCharsNeeded == 0) {
35                // Current window is valid, try to shrink from left
36                int leftCharIndex = word1.charAt(left) - 'a';
37              
38                // Check if removing left character breaks validity
39                if (windowFrequency[leftCharIndex] == targetFrequency[leftCharIndex]) {
40                    // Removing this character will make window invalid
41                    uniqueCharsNeeded++;
42                }
43              
44                // Remove character at left pointer and move left forward
45                windowFrequency[leftCharIndex]--;
46                left++;
47            }
48          
49            // All substrings starting from indices [0, left-1] and ending at right are valid
50            totalValidSubstrings += left;
51        }
52      
53        return totalValidSubstrings;
54    }
55}
56
1class Solution {
2public:
3    long long validSubstringCount(string word1, string word2) {
4        // Early return if word1 is shorter than word2
5        if (word1.size() < word2.size()) {
6            return 0;
7        }
8      
9        // Count frequency of each character in word2
10        int targetFreq[26]{};  // Frequency map for word2 characters
11        int uniqueCharsNeeded = 0;  // Number of unique characters we need to match
12      
13        for (char& ch : word2) {
14            if (++targetFreq[ch - 'a'] == 1) {
15                // This is a new unique character in word2
16                ++uniqueCharsNeeded;
17            }
18        }
19      
20        long long result = 0;
21        int windowFreq[26]{};  // Frequency map for current window
22        int leftPtr = 0;  // Left pointer of sliding window
23      
24        // Iterate through word1 with right pointer
25        for (char& ch : word1) {
26            int charIndex = ch - 'a';
27          
28            // Add current character to window
29            if (++windowFreq[charIndex] == targetFreq[charIndex]) {
30                // We've matched the required frequency for this character
31                --uniqueCharsNeeded;
32            }
33          
34            // Shrink window from left while it's still valid
35            while (uniqueCharsNeeded == 0) {
36                // Current window is valid, try to shrink from left
37                charIndex = word1[leftPtr] - 'a';
38              
39                if (windowFreq[charIndex] == targetFreq[charIndex]) {
40                    // Removing this character will make window invalid
41                    ++uniqueCharsNeeded;
42                }
43              
44                --windowFreq[charIndex];
45                ++leftPtr;
46            }
47          
48            // All substrings starting from indices [0, leftPtr-1] and ending at current position are valid
49            result += leftPtr;
50        }
51      
52        return result;
53    }
54};
55
1/**
2 * Count the number of valid substrings in word1 that contain all characters from word2
3 * with at least the same frequency as in word2
4 * @param word1 - The source string to search for valid substrings
5 * @param word2 - The pattern string containing required characters and frequencies
6 * @returns The total count of valid substrings
7 */
8function validSubstringCount(word1: string, word2: string): number {
9    // Edge case: if word2 is longer than word1, no valid substrings exist
10    if (word2.length > word1.length) {
11        return 0;
12    }
13  
14    // Create frequency map for word2 (required character counts)
15    const requiredFreq: Map<string, number> = new Map();
16    for (const char of word2) {
17        requiredFreq.set(char, (requiredFreq.get(char) || 0) + 1);
18    }
19  
20    // Number of unique characters we need to match
21    const uniqueCharsNeeded: number = requiredFreq.size;
22    let validSubstringCount: number = 0;
23  
24    // Sliding window approach
25    for (let left = 0; left < word1.length; left++) {
26        // Current window frequency map
27        const windowFreq: Map<string, number> = new Map();
28        let charsMatched: number = 0;
29      
30        // Expand window to the right
31        for (let right = left; right < word1.length; right++) {
32            const currentChar: string = word1[right];
33          
34            // Update window frequency for current character
35            windowFreq.set(currentChar, (windowFreq.get(currentChar) || 0) + 1);
36          
37            // Check if current character helps meet requirements
38            if (requiredFreq.has(currentChar)) {
39                const currentCount: number = windowFreq.get(currentChar)!;
40                const requiredCount: number = requiredFreq.get(currentChar)!;
41              
42                // If we just reached the required frequency for this character
43                if (currentCount === requiredCount) {
44                    charsMatched++;
45                }
46            }
47          
48            // If all required characters are matched with sufficient frequency
49            if (charsMatched === uniqueCharsNeeded) {
50                // All substrings from current window to end are valid
51                validSubstringCount += word1.length - right;
52                break;
53            }
54        }
55    }
56  
57    return validSubstringCount;
58}
59

Time and Space Complexity

The time complexity is O(n + m), where n is the length of word1 and m is the length of word2. The algorithm first counts the characters in word2 using Counter, which takes O(m) time. Then it uses a sliding window approach with two pointers to traverse word1 once, where each character is visited at most twice (once when expanding the window and once when shrinking it), resulting in O(n) operations. Therefore, the total time complexity is O(n + m).

The space complexity is O(|Σ|), where Σ represents the character set. The algorithm uses two Counter objects: cnt to store the character frequencies of word2 and win to track the current window's character frequencies. Since the problem deals with lowercase English letters, the size of the character set is at most 26, making the space complexity O(1) or constant space.

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

Common Pitfalls

Pitfall 1: Incorrectly Counting Valid Substrings

The Problem: A common mistake is misunderstanding what result += left represents. Developers often think they need to count substrings only when chars_needed == 0, leading to incorrect implementations like:

# INCORRECT approach
while chars_needed == 0:
    result += 1  # Wrong! This only counts one substring per valid window
    # ... shrink window logic

Why It's Wrong: When we find a valid window ending at position right, ALL substrings ending at right with starting positions from 0 to left-1 are valid. The incorrect approach only counts the minimal valid window.

The Solution: After shrinking the window to find the leftmost valid position, add left to the result. This accounts for all valid substrings ending at the current right position:

# CORRECT approach
while chars_needed == 0:
    # Shrink window to find minimum valid window
    if window_freq[word1[left]] == target_freq[word1[left]]:
        chars_needed += 1
    window_freq[word1[left]] -= 1
    left += 1

# Add all valid substrings ending at 'right'
result += left

Pitfall 2: Mishandling Character Frequency Comparison

The Problem: When checking if a character's frequency matches the target, developers might use:

# INCORRECT - doesn't handle characters not in word2
if char in target_freq and window_freq[char] == target_freq[char]:
    chars_needed -= 1

Why It's Wrong: Python's Counter returns 0 for missing keys, so target_freq[char] returns 0 for characters not in word2. The code should handle this case correctly without explicit checks.

The Solution: The original code handles this elegantly:

# CORRECT - Counter returns 0 for missing keys
if window_freq[char] == target_freq[char]:
    chars_needed -= 1

This works because:

  • If char is not in word2, target_freq[char] = 0
  • When window_freq[char] becomes 0 (or starts at 0 and increments to 1), the condition won't match
  • Only when both frequencies match (including the case where both are 0) do we adjust chars_needed

Pitfall 3: Over-decrementing chars_needed

The Problem: Some implementations might decrement chars_needed every time a required character is added:

# INCORRECT
if char in target_freq:
    window_freq[char] += 1
    if window_freq[char] <= target_freq[char]:
        chars_needed -= 1  # Wrong! This decrements for every occurrence

Why It's Wrong: chars_needed tracks the number of unique characters that have reached their target frequency, not the total count of characters. Decrementing for every occurrence would make chars_needed negative and break the algorithm.

The Solution: Only decrement chars_needed when a character's frequency exactly matches the target:

# CORRECT
window_freq[char] += 1
if window_freq[char] == target_freq[char]:  # Exact match only
    chars_needed -= 1

This ensures each unique character in word2 contributes exactly once to reducing chars_needed from its initial value.

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

What is the best way of checking if an element exists in an unsorted array once in terms of time complexity? Select the best that applies.


Recommended Readings

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

Load More