LeetCode #1358 Medium

Number of Substrings Containing All Three Characters

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.

Constraints
  • 3 <= s.length <= 5 x 10⁴
  • s only consists of 'a', 'b' or 'c' characters.
sliding-windowstringshashmap
Open on LeetCode ↗
02

Intuition

Number of substrings containing all three characters counts substrings of a string over a, b, and c that contain at least one of each. Checking every substring is O(n²). The efficient approach turns on a monotonicity observation: - If a substring from left to right contains all three characters, then extending it further right still contains all three — so once a valid window is found, every longer one is valid too. That means for each right endpoint, the valid left endpoints form a contiguous range starting at 0. Finding the rightmost valid left position immediately gives the count. The cleanest formulation tracks the last seen index of each character. For a given right endpoint, the smallest of those three indices is the latest position where all three are still present. Every left endpoint from 0 up to that index yields a valid substring, so the count contributed is min(lastA, lastB, lastC) + 1. The + 1 accounts for left endpoint 0 being valid, and omitting it undercounts by exactly one per position. Initialising all three last-seen indices to −1 handles the prefix where some character has not yet appeared — the minimum is then −1 and the contribution is 0, which is correct. The sliding-window alternative shrinks from the left while the window remains valid, then adds left + 1. Equivalent, and some find the window framing more familiar than the last-seen indices. One pass with three variables gives O(n) time and O(1) space.

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

Try it first

Before reading on: price up what counting everything costs here, then ask what makes a window invalid, and which pointer should move when it is. Aim for O(n) time and O(1) space.

1

Spot the monotonicity

A valid substring stays valid when extended right, so for each right endpoint the valid left endpoints form a contiguous range from 0.

2

Track last-seen indices

Record the most recent position of each of the three characters. The smallest of them is the latest left endpoint that still includes all three.

3

Initialise to -1

Before a character has appeared, its index is -1, making the minimum -1 and the contribution 0 — correct for the prefix with no valid substrings.

4

Add the count per position

Contribute min(lastA, lastB, lastC) + 1 at each right endpoint. The + 1 counts left endpoint 0 and omitting it undercounts by one per position.

5

Update after counting

Record the current character's index, then move on. Each position is handled in constant time with no inner loop.

6

Know the window alternative

Shrink from the left while the window stays valid, then add left + 1. Equivalent, and the window framing is more familiar to some.

7

Cost of the approach

One pass with three variables gives O(n) time and O(1) space, replacing the O(n²) enumeration.

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.