The load-bearing word is "distinct." If the problem counted every even and odd element, a prefix-sum or sliding window would handle it directly. Distinct values behave differently. Extending a window by one element changes the distinct-even count only when that value is new to the window. Shrinking from the left changes the count only when the removed element was the last copy of its value.
That makes the window state non-monotonic. A number can appear many times, yet it contributes once to the distinct count as long as at least one copy remains in the subarray.
The problem reduces to two sub-questions: how do we track whether a value still belongs to the current subarray, and how do we compare distinct-odd and distinct-even counts efficiently across all subarrays?
nums.length <= 10^5. An O(n^2) scan over all subarrays is around 10^10 operations in the worst case, too slow. The final solution needs to be O(n log n) or better.10^5, so a hash map keyed by value fits comfortably.Pick every possible subarray, scan it, build one set of distinct even values and one set of distinct odd values, and compare their sizes. The longest subarray with equal sizes is the answer.
This is the baseline because it follows the definition directly. It also exposes the bottleneck: we rebuild the same distinct sets from scratch for heavily overlapping subarrays.
answer = 0.l from 0 to n - 1:r from l to n - 1:k from l to r and insert nums[k] into the appropriate set.answer.answer.The inner rescan is pure waste. Fixing the left boundary and extending the right one element at a time lets us update the distinct sets incrementally instead of rebuilding them.
Fix left and grow right one step at a time. The window [left..right + 1] differs from [left..right] by a single new value, so we insert that value into the correct set in O(1) average time rather than rebuilding both sets. For a fixed left, the two sets always hold the distinct values inside the current window, because each step only adds and nothing leaves.
This removes the O(n) inner rescan and drops the running time from O(n^3) to O(n^2). It is still too slow at n = 10^5, because every (left, right) pair is examined.
answer = 0.left from 0 to n - 1:right from left to n - 1.nums[right] into the correct set.answer.answer.Distinct-set maintenance is now cheap, but we still process all O(n^2) subarrays. The next approach transforms the balance condition into a value that a single sweep over the right endpoint can match against, so the best left endpoint falls out of one query instead of an inner loop.
The path to O(n log n) is to stop counting "how many distinct values are inside [l..r]" and reason about latest occurrences instead.
Fix a right endpoint i. For any value x, whether x belongs to the subarray [l..i] depends on one fact: is the latest occurrence of x at or after l? If yes, x appears somewhere in [l..i]. If no, every copy of x sits before l, so x is absent.
Assign each distinct value a contribution: +1 if odd, -1 if even. A subarray is balanced exactly when its distinct-odd and distinct-even counts are equal, which means the total contribution of its distinct values is 0.
Let last[x] be the latest 1-based position of value x. Process positions left to right. After handling position i, define tree[p] as the total contribution of all distinct values whose latest occurrence is at or before p, and now as the total contribution of all distinct values seen so far (equivalently tree[i]). The values counted in tree[p] are precisely the ones whose only remaining copies lie in [1..p], so they are absent from the subarray (p + 1) .. i. The contribution of the distinct values that do fall inside (p + 1) .. i is therefore now - tree[p], and the subarray is balanced exactly when tree[p] == now.
For each right endpoint i, we want the earliest p with tree[p] == now, which maximizes the length i - p. When x arrives at position i, its latest occurrence moves: if it appeared before at old, remove its contribution from the suffix [old, n] and add the new contribution to [i, n]. Suffix range-add plus an earliest-position query is what a lazy segment tree handles in O(log n) per step.
Two facts make the query valid. First, tree[n] accumulates every update, since every suffix add ends at n, so tree[n] == now. The target now is therefore always present somewhere, and a balanced cut always exists (at worst the empty range at p = i). Second, each tree node stores the mn and mx of its range, so now appears in a subtree only if mn <= now <= mx there. The query descends into the left child whenever the target falls in its range, so when both children contain now it still goes left. That returns the smallest position p with tree[p] == now, the earliest balanced cut, which maximizes i - p.
[0, n], initialized to all zeros.last for the latest 1-based occurrence of each value.now = 0 and answer = 0.i from 1 to n:x = nums[i - 1] and det = +1 if x is odd, otherwise -1.x appeared before at last[x], apply modify(last[x], n, -det) and subtract det from now.last[x] = i.modify(i, n, det) and add det to now.p where the segment tree value equals now.answer = max(answer, i - p).answer.