Count Substrings That Can Be Rearranged to Contain a String II
HardPrompt
You are given two strings word1 and word2.
A string x is called valid if x can be rearranged to have word2 as a prefix.
Return the total number of valid substrings of word1.
Note that the memory limits in this problem are smaller than usual, so you must implement a solution with a linear runtime complexity.
Example 1:
Input: word1 = "bcca", word2 = "abc"
Output: 1
Explanation:
The only valid substring is "bcca" which can be rearranged to "abcc" having "abc" as a prefix.
Example 2:
Input: word1 = "abcabc", word2 = "abc"
Output: 10
Explanation:
All the substrings except substrings of size 1 and size 2 are valid.
Example 3:
Input: word1 = "abcabc", word2 = "aaabc"
Output: 0
Constraints:
1 <= word1.length <= 1061 <= word2.length <= 104word1andword2consist only of lowercase English letters.
Approaches
2 approaches with complexity analysis and trade-offs.
This method involves checking every possible substring of word1. For each substring, we determine if it's "valid" by comparing its character counts with the character counts of word2. A substring is valid if it contains at least as many of each character as word2 does.
Algorithm
- Create a frequency map
targetFreqforword2. - Initialize
count = 0. - Loop
ifrom0toword1.length() - 1:- Create an empty frequency map
currentFreq. - Loop
jfromitoword1.length() - 1:- Add
word1.charAt(j)tocurrentFreq. - Check if
currentFreqis sufficient compared totargetFreqby iterating through all 26 characters. - If it is, increment
count.
- Add
- Create an empty frequency map
- Return
count.
Walkthrough
First, we pre-calculate the character frequency map of word2. This map, let's call it targetFreq, will be used as the reference for our validity checks.
We then use two nested loops to generate all substrings of word1. The outer loop selects the starting index i, and the inner loop selects the ending index j.
For each starting index i, we initialize a currentFreq map for the characters. As the inner loop progresses from j = i to the end of the string, we incrementally build the substring and update currentFreq.
At each step j, we have the frequency map for the substring word1[i..j]. We then check if this substring is valid by comparing currentFreq with targetFreq. If the substring is valid, we increment a total counter. After checking all substrings, the total counter gives the final answer.
class Solution { public long countSubstrings(String word1, String word2) { int[] targetFreq = new int[26]; for (char c : word2.toCharArray()) { targetFreq[c - 'a']++; } long validCount = 0; int n = word1.length(); for (int i = 0; i < n; i++) { int[] currentFreq = new int[26]; for (int j = i; j < n; j++) { currentFreq[word1.charAt(j) - 'a']++; if (isSufficient(currentFreq, targetFreq)) { // If word1[i..j] is valid, all substrings word1[i..k] where k > j are also valid. // We can optimize by adding (n - j) and breaking the inner loop. // But for a basic brute force, we check each one. validCount++; } } } return validCount; } private boolean isSufficient(int[] currentFreq, int[] targetFreq) { for (int k = 0; k < 26; k++) { if (currentFreq[k] < targetFreq[k]) { return false; } } return true; }}Complexity
Time
O(N^2 * A), where N is the length of `word1` and A is the alphabet size (26). The nested loops run in O(N^2) and the frequency comparison inside takes O(A).
Space
O(A), where A is the alphabet size (26). This is for storing the frequency maps.
Trade-offs
Pros
Simple to conceptualize and implement.
Correct for smaller inputs.
Cons
Highly inefficient due to its quadratic time complexity.
Will result in a 'Time Limit Exceeded' error for the input sizes specified in the problem constraints.
A much more efficient approach uses the sliding window technique. The core idea is that if a substring word1[i..j] is valid, then any longer substring starting at i (e.g., word1[i..j+1]) is also valid. This property allows us to avoid re-checking every single substring. We can find the first valid substring starting at i and then quickly count all subsequent valid ones.
Algorithm
- Pre-calculate
targetFreqforword2and count the number of unique characters in it, let's call thismismatches. - Initialize
totalCount = 0, left pointeri = 0, right pointerj = 0, and an emptycurrentFreqmap. - Loop
ifrom0toword1.length() - 1:- While
j < word1.length()andmismatches > 0:- Add
word1.charAt(j)tocurrentFreq. - If
currentFreqfor this character now matchestargetFreq, decrementmismatches. - Increment
j.
- Add
- If
mismatches == 0, it meansword1[i..j-1]is the first valid substring starting ati. All substrings starting atiand ending atj-1or later are also valid. Addword1.length() - (j - 1)tototalCount. - Shrink the window from the left: Remove
word1.charAt(i)fromcurrentFreq. - If
currentFreqfor this character was exactly matchingtargetFreqbefore removal, it becomes deficient, so incrementmismatches.
- While
- Return
totalCount.
Walkthrough
We use two pointers, i (left) and j (right), to define a "window" on word1. The pointer i iterates from the beginning to the end of word1, representing the start of our potential substrings.
For each i, we expand the window by advancing j until the substring word1[i..j] becomes valid. To efficiently track validity, we maintain a count of "deficient" character types. Initially, this is the number of unique characters in word2. As we expand the window by including word1.charAt(j), we update our window's frequency map. If the count for a character c in our window matches the required count from word2, we decrement the deficient count.
Once the deficient count drops to zero, we have found the shortest valid substring starting at i. Let's say it ends at index j-1. Then we know that all substrings starting at i and ending at any index from j-1 to N-1 (where N is word1.length()) are also valid. The number of such substrings is N - (j-1). We add this to our total count.
We then slide the window forward by incrementing i. This involves removing word1.charAt(i) from our window's frequency map and updating the deficient count if this removal makes a previously satisfied character deficient again. The right pointer j does not reset, ensuring that each character in word1 is visited at most twice.
class Solution { public long countSubstrings(String word1, String word2) { int n = word1.length(); int[] targetFreq = new int[26]; for (char c : word2.toCharArray()) { targetFreq[c - 'a']++; } int mismatches = 0; for (int count : targetFreq) { if (count > 0) { mismatches++; } } long totalCount = 0; int[] currentFreq = new int[26]; int i = 0, j = 0; for (i = 0; i < n; i++) { // Expand window by moving right pointer j while (j < n && mismatches > 0) { char charJ = word1.charAt(j); currentFreq[charJ - 'a']++; if (currentFreq[charJ - 'a'] == targetFreq[charJ - 'a']) { mismatches--; } j++; } // If window is valid, count all substrings starting at i if (mismatches == 0) { totalCount += (long)n - j + 1; } // Shrink window from the left by moving pointer i char charI = word1.charAt(i); if (targetFreq[charI - 'a'] > 0 && currentFreq[charI - 'a'] == targetFreq[charI - 'a']) { mismatches++; } currentFreq[charI - 'a']--; } return totalCount; }}Complexity
Time
O(N + M), where N is `word1.length()` and M is `word2.length()`. `M` for pre-calculation, and `N` because both pointers `i` and `j` traverse `word1` only once.
Space
O(A), where A is the alphabet size (26). This is for the frequency maps, which is constant space.
Trade-offs
Pros
Optimal time complexity (linear), as required by the problem.
Efficient memory usage (constant space).
Guaranteed to pass within the given time and memory constraints.
Cons
The logic is more complex than the brute-force approach and requires careful implementation to handle window expansion and shrinking correctly.
Solutions
Solution
class Solution {public long validSubstringCount(String word1, String word2) { if (word1.length() < word2.length()) { return 0; } int[] cnt = new int[26]; int need = 0; for (int i = 0; i < word2.length(); ++i) { if (++cnt[word2.charAt(i) - 'a'] == 1) { ++need; } } long ans = 0; int[] win = new int[26]; for (int l = 0, r = 0; r < word1.length(); ++r) { int c = word1.charAt(r) - 'a'; if (++win[c] == cnt[c]) { --need; } while (need == 0) { c = word1.charAt(l) - 'a'; if (win[c] == cnt[c]) { ++need; } --win[c]; ++l; } ans += l; } return ans; }}class Solution {public: long long validSubstringCount(string word1, string word2) { if (word1.size() < word2.size()) { return 0; } int cnt[26]{}; int need = 0; for (char &c : word2) { if (++cnt[c - 'a'] == 1) { ++need; } } long long ans = 0; int win[26]{}; int l = 0; for (char &c : word1) { int i = c - 'a'; if (++win[i] == cnt[i]) { --need; } while (need == 0) { i = word1[l] - 'a'; if (win[i] == cnt[i]) { ++need; } --win[i]; ++l; } ans += l; } return ans; }};class Solution: def validSubstringCount(self, word1: str, word2: str) -> int: if len(word1) < len(word2): return 0 cnt = Counter(word2) need = len(cnt) ans = l = 0 win = Counter() for c in word1: win[c] += 1 if win[c] == cnt[c]: need -= 1 while need == 0: if win[word1[l]] == cnt[word1[l]]: need += 1 win[word1[l]] -= 1 l += 1 ans += l return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.