Count Substrings That Can Be Rearranged to Contain a String I
MedPrompt
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.
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 <= 1051 <= word2.length <= 104word1andword2consist only of lowercase English letters.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach involves iterating through every possible substring of word1. For each substring, we calculate its character frequency and check if it meets the criteria to be a "valid" string. A substring is considered valid if its character counts are greater than or equal to the character counts of word2 for every character in the alphabet. While straightforward, this method is computationally expensive.
Algorithm
- Pre-calculate the character frequency map of
word2, let's call itfreq2. - Initialize a counter for valid substrings,
count, to 0. - Use nested loops to generate all substrings of
word1. The outer loop iterates through the starting indexifrom0ton-1, and the inner loop iterates through the ending indexjfromiton-1. - For each substring
word1[i...j], maintain a running character frequency map,currentFreq. - After adding
word1.charAt(j)to the window, check ifcurrentFreqhas at least as many of each character asfreq2. - If the condition is met, it means the substring
word1[i...j]is valid, so increment thecount. - After iterating through all substrings, return the total
count.
Walkthrough
The brute-force method systematically checks every substring. We can optimize the process slightly by not re-calculating the frequency map for each substring from scratch. For a fixed starting point i, as we extend the substring by incrementing the end point j, we can update the frequency map of the current substring in O(1) time. Then, for each new substring word1[i...j], we perform a check against the frequency map of word2.
class Solution { public long countSubstrings(String word1, String word2) { int n = word1.length(); int m = word2.length(); long count = 0; int[] freq2 = new int[26]; for (char c : word2.toCharArray()) { freq2[c - 'a']++; } 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, freq2)) { count++; } } } return count; } private boolean isSufficient(int[] freq1, int[] freq2) { for (int i = 0; i < 26; i++) { if (freq1[i] < freq2[i]) { return false; } } return true; }}Complexity
Time
O(N^2 * C), where N is the length of `word1` and C is the size of the character set (26). The two nested loops iterate through all O(N^2) substrings, and for each, the check takes O(C) time.
Space
O(C), where C is the size of the character set (26). This space is used to store the frequency maps.
Trade-offs
Pros
Simple to understand and implement.
Correctly solves the problem for small inputs.
Cons
This approach is too slow for the given constraints (
word1.lengthup to 10^5), as an O(N^2) complexity will lead to a 'Time Limit Exceeded' error.
A more efficient solution uses the sliding window technique. The core idea relies on a key property: if a substring word1[i...j] is valid, then any longer substring starting at the same position i (e.g., word1[i...k] where k > j) must also be valid. This monotonicity allows us to avoid re-checking many substrings. We can find the smallest valid substring starting at each position i and then quickly calculate how many valid substrings start at that position.
Algorithm
- First, compute the character frequency map for
word2, let's call itfreq2. - Initialize two pointers,
left = 0andright = 0, to represent the current windowword1[left...right-1]. - Initialize a frequency map for the window,
windowFreq, and a total count of valid substrings,count = 0. - Iterate with the
leftpointer from0ton-1. - For each
left, expand the window by advancing therightpointer until the windowword1[left...right-1]becomes valid (i.e., its character counts are sufficient). - Once a valid window
word1[left...right-1]is found, we know that any substring starting atleftand ending atright-1or later is also valid. The number of such substrings isn - (right - 1). Add this tocount. - After processing
left, shrink the window by decrementing the count ofword1.charAt(left)and advancingleftto the next position. - The
rightpointer does not reset, which is key to the algorithm's efficiency. - Return the total
count.
Walkthrough
We use two pointers, left and right, to maintain a window over word1. The left pointer iterates through all possible starting positions of a substring. For each left, we expand the window by moving right forward until the substring word1[left...right-1] contains enough characters to satisfy the requirements of word2. Once this minimal valid window is found, we know that this window, and any window starting at left that extends beyond right-1, are all valid. The number of such valid substrings is n - (right - 1). We add this to our total count. Then, we slide the window forward by incrementing left and removing word1.charAt(left) from our window's frequency count. The right pointer continues from where it was, ensuring that each character in word1 is visited by left and right at most once.
class Solution { public long countSubstrings(String word1, String word2) { int n = word1.length(); int m = word2.length(); long count = 0; int[] freq2 = new int[26]; for (char c : word2.toCharArray()) { freq2[c - 'a']++; } int[] windowFreq = new int[26]; int right = 0; for (int left = 0; left < n; left++) { // Expand the window by moving 'right' until the substring is valid while (right < n && !isSufficient(windowFreq, freq2)) { windowFreq[word1.charAt(right) - 'a']++; right++; } // If the window is valid, all substrings starting at 'left' and ending at 'right-1' or later are valid. if (isSufficient(windowFreq, freq2)) { count += (long)n - (right - 1); } // Shrink the window from the left for the next iteration windowFreq[word1.charAt(left) - 'a']--; } return count; } private boolean isSufficient(int[] freq1, int[] freq2) { for (int i = 0; i < 26; i++) { if (freq1[i] < freq2[i]) { return false; } } return true; }}Complexity
Time
O(N + M), where N is the length of `word1` and M is the length of `word2`. The `left` and `right` pointers each traverse `word1` at most once, leading to O(N) time for the main loop. Calculating the frequency map for `word2` takes O(M) time.
Space
O(C), where C is the size of the character set (26). This constant space is used for the frequency maps.
Trade-offs
Pros
Highly efficient with linear time complexity.
Optimal solution for the given constraints.
Cons
The logic is more complex to reason about compared to the brute-force approach.
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.