We need to find every index i (from 1 to n-1) where we cut the string into s[0..i-1] and s[i..n-1], and both halves have the same number of distinct characters.
The brute force idea is to try every split point and count distinct characters on each side. Counting from scratch at every position repeats work. As the split point slides one position to the right, only one character moves from the right half to the left half. The distinct counts change incrementally, so we can update them in O(1) per step instead of recounting.
1 <= s.length <= 10^5 → We need O(n) or O(n log n). An O(n^2) brute force that rebuilds sets at every split will be too slow.s consists of only lowercase English letters → At most 26 distinct characters. This means any frequency map or set we maintain has bounded size, so operations on it are O(1).For every possible split position, build a set of characters for the left half and a set for the right half, then check if their sizes match. There are n-1 split positions, and at each one we scan both substrings.
i from 1 to n-1:s[0..i-1] using a set.s[i..n-1] using a set.Rebuilding both sets at every split point is the source of the quadratic cost. The next approach precomputes the distinct count from each end once, so every split check becomes O(1).
Instead of recomputing distinct counts at every split, we precompute them. Scan left-to-right and record how many distinct characters appear up to each index. Do the same from right-to-left. For each split point, compare the two precomputed values.
This follows the prefix sum pattern, except we track running set sizes instead of running totals.
prefixDistinct of size n, where prefixDistinct[i] is the number of distinct characters in s[0..i].suffixDistinct of size n, where suffixDistinct[i] is the number of distinct characters in s[i..n-1].i from 1 to n-1, check if prefixDistinct[i-1] == suffixDistinct[i].A split at index i divides the string into s[0..i-1] and s[i..n-1]. By construction, prefixDistinct[i-1] is the distinct count of the left half and suffixDistinct[i] is the distinct count of the right half, so comparing them tests exactly the split condition. The arrays are also monotonic (prefix non-decreasing left-to-right, suffix non-increasing left-to-right), which matches the intuition that distinct counts only grow as a half absorbs more characters.
This approach runs in O(n) time but allocates two arrays of size n. The next approach keeps the same time bound while replacing both arrays with a fixed set of counters, dropping the space to O(1).
Start by putting all character frequencies into a "right" frequency map. Then sweep the split point from left to right. At each step, move the current character from the right side to the left side. When a character is added to the left for the first time, the left distinct count goes up by one. When a character's frequency in the right map drops to zero, the right distinct count goes down by one. Tracking these two counts is enough to test every split.
This replaces both arrays with a single set of counters and solves the problem in one sweep after the initial frequency count.
s (this represents the "right" side initially).rightDistinct as the number of keys in this map and leftDistinct as 0.i from 0 to n-2 (these are the split points):s[i] to the left side: if it's the first occurrence on the left, increment leftDistinct.s[i] from the right side: decrement its frequency. If frequency reaches 0, decrement rightDistinct.leftDistinct == rightDistinct, this is a good split.