3297. Count Substrings That Can Be Rearranged to Contain a String I
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.
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:
- Expand the window by moving the right pointer and adding characters
- When the window contains all required characters from
word2
, we have a valid window - Shrink from the left to find the minimum valid window ending at the current right position
- 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, return0
immediately since no valid substring can exist - Create a frequency counter
cnt
forword2
usingCounter(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, andwin = Counter()
for the current window's character frequencies
Main Algorithm:
For each character c
at position r
in word1
:
-
Expand the window: Add character
c
to the window by incrementingwin[c] += 1
-
Check if requirement met: If
win[c] == cnt[c]
, it means characterc
now meets its required frequency, so decrementneed -= 1
-
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 incrementneed += 1
- Decrement
win[word1[l]] -= 1
to remove the leftmost character from the window - Move left pointer forward:
l += 1
- If
- The loop exits when the window becomes invalid (
need > 0
)
-
Count valid substrings: After shrinking, add
l
to the answer. This represents all substrings ending at positionr
with starting positions from0
tol-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 EvaluatorExample 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), soneed = 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), soneed = 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), soneed
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), soneed = 0
- Window is valid! Current window is [0, 3] = "bcca"
- Now shrink from left:
- At
l = 0
: character 'b' haswin['b'] = 1 = cnt['b']
, removing it breaks validity - Set
need = 1
, decrementwin['b'] = 0
, movel = 1
- Exit shrinking loop since
need > 0
- At
- 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 inword2
,target_freq[char] = 0
- When
window_freq[char]
becomes0
(or starts at0
and increments to1
), 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.
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
https assets algo monster cover_photos stack svg Sliding Window Maximum Monotonic Stack We have an array and a sliding window defined by a start index and an end index The sliding window moves from left of the array to right There are always k elements in the window The window
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!