LeetCode #1358 Medium

Number of Substrings Containing All Three Characters

Given a string of only a, b, and c, count the substrings that contain at least one of each.

sliding-windowstringshashmap
Open on LeetCode ↗
02

Intuition

The trap is reaching for a sliding window or, worse, enumerating every substring and checking each one for all three characters, which is O(n^2) before you even count. Instead fix the right end and ask how many valid substrings end there. If the last occurrences of a, b, and c are at positions la, lb, lc, then any start index at or before min(la, lb, lc) gives a substring containing all three. That is min(la, lb, lc) + 1 substrings, added at every position — a counting trick that needs three integers and no window at all.

How to spot this pattern

For each right endpoint, count the valid left endpoints directly. Tracking the last seen index of each of a, b, c means min(last) + 1 is exactly how many starting positions produce a substring containing all three — no window shrinking needed.

03

Approach

1

Count by right endpoint

Enumerating all substrings is O(n^2) even before checking each one. Instead, for each right endpoint count how many left endpoints work. Summing those counts covers every substring exactly once, since each substring has exactly one right endpoint.

2

Track the last seen index of each character

Keep last = [-1, -1, -1] for a, b, and c, updating last[ord(ch) - ord('a')] = i at each step. A substring s[j..i] contains all three exactly when j is at or before every one of those three positions — that is, j <= min(last).

3

Add min(last) + 1 each step

The valid starts are 0, 1, ..., min(last), so there are min(last) + 1 of them. If any character has not appeared yet, min(last) is -1 and the term is 0, which handles the prefix correctly with no special case. Sum across all i for the answer. One pass, O(n) time, O(1) space.

04

Solution & live demo

1class Solution:
2 def numberOfSubstrings(self, s):
3 last = [-1, -1, -1]
4 total = 0
5 for i, ch in enumerate(s):
6 last[ord(ch) - ord('a')] = i
7 total += min(last) + 1
8 return total
05

Common pitfalls

Adding 1 per valid window

✗ Wrong
if min(last) >= 0: total += 1
✓ Right
total += min(last) + 1

Every start from 0 through min(last) yields a valid substring ending here — that's min(last) + 1 of them, not one. Counting singly undercounts massively.

Forgetting the -1 initialisation does the guarding

✗ Wrong
last = [0, 0, 0]
✓ Right
last = [-1, -1, -1]

Seeding with 0 claims each character was seen at index 0, so counting begins before all three have appeared. Starting at −1 makes min(last) + 1 evaluate to 0 until every character has been seen at least once.

Using a shrinking window

✗ Wrong
while have all three: shrink and count
✓ Right
last[ord(ch) - ord('a')] = i
total += min(last) + 1

The window approach works but needs a frequency map and a shrink loop. Three last-seen indices carry the same information in constant space with no inner loop at all.

06

Edge cases

String shorter than 3

Some last value stays -1 throughout, so the sum is 0.

String with only one distinct character

Same — the answer is 0.

"abc"

Only the last position contributes, adding 0 + 1 = 1.

Long runs of a single character

Handled naturally — min(last) stops advancing, so each position adds the same count.

07

Complexity

Time
O(n)
Space
O(1)
Three integers. Counting by right endpoint replaces the O(n^2) enumeration.