Given a string s
consisting only of characters a, b and c.
Return the number of substrings containing at least one occurrence of all these characters a, b and c.
Example 1:
Input: s = "abcabc"
Output: 10
Explanation: The substrings containing at least one occurrence of the characters a, b and c are "abc", "abca", "abcab", "abcabc", "bca", "bcab", "bcabc", "cab", "cabc" and "abc" (again).
Example 2:
Input: s = "aaacb"
Output: 3
Explanation: The substrings containing at least one occurrence of the characters a, b and c are "aaacb", "aacb" and "acb".
Example 3:
Input: s = "abc"
Output: 1
Constraints:
3 <= s.length <= 5 x 10^4
s
only consists of a, b or c characters.Given a string s
consisting only of characters a, b and c, the task is to return the number of substrings containing at least one occurrence of all these characters a, b and c.
The brute-force solution involves generating all possible substrings of the given string and checking if each substring contains at least one occurrence of each character 'a', 'b', and 'c'.
def count_substrings_naive(s):
n = len(s)
count = 0
for i in range(n):
for j in range(i, n):
sub = s[i:j+1]
if 'a' in sub and 'b' in sub and 'c' in sub:
count += 1
return count
A more efficient approach uses a sliding window technique. We maintain a window and expand it to the right until it contains at least one 'a', 'b', and 'c'. Then, we shrink the window from the left until one of the required characters is no longer present. For each valid window, we can calculate the number of substrings that include that window.
def count_substrings(s):
n = len(s)
count = 0
left = 0
freq = {'a': 0, 'b': 0, 'c': 0}
for right in range(n):
freq[s[right]] += 1
while freq['a'] > 0 and freq['b'] > 0 and freq['c'] > 0:
count += (n - right)
freq[s[left]] -= 1
left += 1
return count
For s = "abcabc"
:
left = 0
, freq = {'a': 0, 'b': 0, 'c': 0}
.right
iterates from 0 to 5.right = 2
, freq = {'a': 1, 'b': 1, 'c': 1}
. The condition is met.count += (6 - 2) = 4
. freq
becomes {'a': 0, 'b': 1, 'c': 1}
, and left
becomes 1.right = 3
, freq = {'a': 1, 'b': 1, 'c': 1}
. The condition is met.count += (6 - 3) = 3
. freq
becomes {'a': 1, 'b': 0, 'c': 1}
, and left
becomes 2.right = 4
, freq = {'a': 1, 'b': 1, 'c': 1}
. The condition is met.count += (6 - 4) = 2
. freq
becomes {'a': 1, 'b': 1, 'c': 0}
, and left
becomes 3.right = 5
, freq = {'a': 1, 'b': 1, 'c': 1}
. The condition is met.count += (6 - 5) = 1
. freq
becomes {'a': 1, 'b': 1, 'c': 0}
, and left
becomes 4.The final count is 4 + 3 + 2 + 1 = 10.
The optimal solution has a time complexity of O(n), where n is the length of the string. This is because we iterate through the string once with the right
pointer and, in the worst case, the left
pointer also iterates through the string once. The operations inside the loops are constant time operations.
The naive solution has a time complexity of O(n^3) because we are generating all possible substrings which takes O(n^2) time and for each substring we are checking for presence of a, b, c which takes O(n) time in the worst case.
The optimal solution uses a constant amount of extra space for the frequency map freq
, which stores the counts of characters 'a', 'b', and 'c'. Therefore, the space complexity is O(1).
The naive solution has space complexity of O(n) since we are storing the substring of length n in the worst case.
These edge cases are handled correctly by the optimal solution, as the while
loop condition will never be met if any of the characters are missing, and count
will remain 0.