3719. Longest Balanced Subarray I
Problem Description
You are given an integer array nums.
A subarray is a contiguous, non-empty sequence of elements within the array. A subarray is called balanced if the number of distinct even numbers in it is equal to the number of distinct odd numbers in it.
Your task is to return the length of the longest balanced subarray.
A few key points to keep in mind:
- Only distinct values are counted. For example, if the subarray is
[2, 2, 3], the count of distinct even numbers is1(just the value2), and the count of distinct odd numbers is also1(the value3), so this subarray is balanced with length3. - The subarray must be contiguous — you cannot skip elements.
- If multiple balanced subarrays exist, you only need the length of the longest one.
Example:
For nums = [2, 2, 3]:
- The subarray
[2, 3]has1distinct even number and1distinct odd number — balanced, length2. - The subarray
[2, 2, 3]also has1distinct even and1distinct odd — balanced, length3.
So the answer is 3.
How We Pick the Algorithm
Why Hash Table / Counting?
This problem maps to Hash Table / Counting through a short path in the full flowchart.
Tracking distinct even and odd values in a growing window requires a hash set to determine whether each new element is a first occurrence.
Open in FlowchartIntuition
The condition for a balanced subarray depends on distinct even and odd values, not just raw counts. This makes the problem tricky for classic tricks like prefix sums, because adding one more element to a subarray may or may not change the distinct counts — it depends on whether that value has already appeared inside the current window.
This observation points us toward a more direct strategy: since the "distinct" property is tied to a specific window's contents, the most natural way to handle it is to fix the left endpoint i and then extend the right endpoint j one step at a time. As the window grows to the right, elements are only ever added, never removed — and that's the key insight. When a window only grows, maintaining the set of seen values is easy:
- Keep a hash set
visof values already in the window. - When
nums[j]is new (not invis), it contributes one new distinct value — increment either the even counter or the odd counter based on its parity (nums[j] & 1). - When
nums[j]is a repeat, the distinct counts don't change at all.
With a counter array cnt of size 2 tracking distinct evens (cnt[0]) and distinct odds (cnt[1]), checking whether the current window [i, j] is balanced becomes a constant-time comparison cnt[0] == cnt[1]. Whenever it holds, the window length j - i + 1 is a candidate for the answer.
By restarting the set and counters for every left endpoint, we examine all O(n^2) subarrays, but each extension step costs only O(1) amortized work thanks to the hash set. This brute-force-with-bookkeeping approach is simple, correct, and avoids the difficulty of trying to shrink a window (where removing an element would require knowing whether other copies of it still remain inside).
Pattern Learn more about Segment Tree, Divide and Conquer and Prefix Sum patterns.
Solution Approach
Pattern used: Enumeration (brute force over left endpoints) + Hash Table for de-duplication.
We enumerate every possible left endpoint i, then extend the right endpoint j from i to the end of the array, maintaining the distinct counts incrementally as the window grows.
Step-by-step walkthrough:
-
Initialize the answer
ans = 0and letnbe the length ofnums. -
Outer loop — fix the left endpoint
i: For eachifrom0ton - 1, reset the bookkeeping structures:cnt = [0, 0]— a counter array wherecnt[0]is the number of distinct even values andcnt[1]is the number of distinct odd values in the current window.vis = set()— a hash set storing all values already seen in the window[i, j].
-
Inner loop — extend the right endpoint
j: For eachjfromiton - 1:- New value check: If
nums[j]is not invis, it is a brand-new distinct value, so:- Increment the appropriate counter using its parity:
cnt[nums[j] & 1] += 1. The bitwise tricknums[j] & 1evaluates to0for even numbers and1for odd numbers, which maps directly to the index incnt. - Add
nums[j]tovis.
- Increment the appropriate counter using its parity:
- Repeat value: If
nums[j]is already invis, the distinct counts are unchanged, so we do nothing. - Balance check: If
cnt[0] == cnt[1], the window[i, j]is balanced, so update the answer:ans = max(ans, j - i + 1).
- New value check: If
-
Return
ansafter all pairs(i, j)have been considered.
Code:
class Solution:
def longestBalanced(self, nums: List[int]) -> int:
n = len(nums)
ans = 0
for i in range(n):
cnt = [0, 0]
vis = set()
for j in range(i, n):
if nums[j] not in vis:
cnt[nums[j] & 1] += 1
vis.add(nums[j])
if cnt[0] == cnt[1]:
ans = max(ans, j - i + 1)
return ans
Why this works: Every subarray of nums corresponds to exactly one pair (i, j) with i <= j, and the double loop visits all such pairs. Because the window only ever grows to the right for a fixed i, the hash set vis lets us detect duplicates in O(1) average time, keeping the counters exact at every step.
Complexity Analysis:
- Time complexity:
O(n^2)— there areO(n^2)pairs(i, j), and each inner-loop step does only constant-time set lookup, insertion, and comparison. - Space complexity:
O(n)— the hash setviscan hold up tondistinct values in the worst case;cntuses constant space.
Example Walkthrough
Let's trace the algorithm on nums = [1, 2, 2, 3]. The expected answer is 3, achieved by the subarray [1, 2, 2] (or [2, 2, 3]), which contains 1 distinct even value (2) and 1 distinct odd value (1 or 3).
Left endpoint i = 0 — reset cnt = [0, 0], vis = {}:
j | nums[j] | New value? | Update | cnt (even, odd) | Balanced? | ans |
|---|---|---|---|---|---|---|
| 0 | 1 | Yes | cnt[1] += 1, vis = {1} | [0, 1] | No | 0 |
| 1 | 2 | Yes | cnt[0] += 1, vis = {1, 2} | [1, 1] | Yes → length 2 | 2 |
| 2 | 2 | No (repeat) | nothing changes | [1, 1] | Yes → length 3 | 3 |
| 3 | 3 | Yes | cnt[1] += 1, vis = {1, 2, 3} | [1, 2] | No | 3 |
Notice the key moment at j = 2: the second 2 is already in vis, so the distinct counts stay [1, 1] and the window [1, 2, 2] remains balanced — the window grows to length 3 "for free."
Left endpoint i = 1 — reset cnt = [0, 0], vis = {}:
j | nums[j] | New value? | cnt | Balanced? | ans |
|---|---|---|---|---|---|
| 1 | 2 | Yes | [1, 0] | No | 3 |
| 2 | 2 | No | [1, 0] | No | 3 |
| 3 | 3 | Yes | [1, 1] | Yes → length 3 | 3 |
The window [2, 2, 3] is also balanced with length 3, but it doesn't improve the answer.
Left endpoint i = 2 — windows [2] gives cnt = [1, 0] (not balanced), then [2, 3] gives cnt = [1, 1] (balanced, length 2, no improvement).
Left endpoint i = 3 — the window [3] gives cnt = [0, 1], never balanced.
All pairs (i, j) have been examined, and the final answer is 3.
This trace highlights the two pillars of the approach: the hash set vis keeps duplicates from inflating the counts (the second 2 changed nothing), and the constant-time check cnt[0] == cnt[1] lets us validate every one of the O(n^2) windows with only O(1) extra work per extension.
Solution Implementation
1from typing import List
2
3
4class Solution:
5 def longestBalanced(self, nums: List[int]) -> int:
6 n = len(nums)
7 max_length = 0
8
9 # Enumerate every possible starting index of the subarray
10 for left in range(n):
11 # count[0]: number of distinct even values in the current window
12 # count[1]: number of distinct odd values in the current window
13 count = [0, 0]
14 # Track distinct values already counted in the window [left, right]
15 seen = set()
16
17 # Extend the subarray to the right one element at a time
18 for right in range(left, n):
19 # Only count a value the first time it appears in the window
20 if nums[right] not in seen:
21 # nums[right] & 1 is 0 for even numbers, 1 for odd numbers
22 count[nums[right] & 1] += 1
23 seen.add(nums[right])
24
25 # The window is balanced when distinct evens == distinct odds
26 if count[0] == count[1]:
27 max_length = max(max_length, right - left + 1)
28
29 return max_length
301class Solution {
2 public int longestBalanced(int[] nums) {
3 int n = nums.length;
4 int ans = 0;
5
6 // Enumerate every possible starting index of the subarray
7 for (int i = 0; i < n; ++i) {
8 // Track the distinct values seen in the current subarray [i, j]
9 Set<Integer> visited = new HashSet<>();
10
11 // count[0]: number of distinct even values
12 // count[1]: number of distinct odd values
13 int[] count = new int[2];
14
15 // Extend the subarray to the right, one element at a time
16 for (int j = i; j < n; ++j) {
17 // Only count the value if it appears for the first time in [i, j]
18 if (visited.add(nums[j])) {
19 // nums[j] & 1 is 0 for even numbers and 1 for odd numbers
20 ++count[nums[j] & 1];
21 }
22
23 // The subarray is balanced when the number of distinct
24 // even values equals the number of distinct odd values
25 if (count[0] == count[1]) {
26 ans = Math.max(ans, j - i + 1);
27 }
28 }
29 }
30
31 return ans;
32 }
33}
341class Solution {
2public:
3 int longestBalanced(vector<int>& nums) {
4 int n = nums.size();
5 int ans = 0;
6
7 // Enumerate every possible starting index of the subarray.
8 for (int i = 0; i < n; ++i) {
9 // Track the distinct values seen in the current window [i, j].
10 unordered_set<int> visited;
11
12 // count[0]: number of distinct even values in the window.
13 // count[1]: number of distinct odd values in the window.
14 int count[2]{};
15
16 // Extend the subarray to the right one element at a time.
17 for (int j = i; j < n; ++j) {
18 // Only count a value the first time it appears in the window,
19 // since we care about distinct elements.
20 if (!visited.contains(nums[j])) {
21 visited.insert(nums[j]);
22 ++count[nums[j] & 1]; // nums[j] & 1 == 1 for odd, 0 for even.
23 }
24
25 // The subarray is balanced when the number of distinct even
26 // values equals the number of distinct odd values.
27 if (count[0] == count[1]) {
28 ans = max(ans, j - i + 1);
29 }
30 }
31 }
32
33 return ans;
34 }
35};
361/**
2 * Finds the length of the longest subarray in which the number of
3 * distinct even values equals the number of distinct odd values.
4 *
5 * @param nums - The input array of integers.
6 * @returns The length of the longest balanced subarray.
7 */
8function longestBalanced(nums: number[]): number {
9 const n: number = nums.length;
10 // Stores the maximum length found so far
11 let ans: number = 0;
12
13 // Enumerate every possible starting index of the subarray
14 for (let i = 0; i < n; ++i) {
15 // Tracks the distinct values seen in the current subarray
16 const visited: Set<number> = new Set<number>();
17 // count[0]: number of distinct even values
18 // count[1]: number of distinct odd values
19 const count: number[] = Array(2).fill(0);
20
21 // Extend the subarray to every possible ending index
22 for (let j = i; j < n; ++j) {
23 // Only count a value the first time it appears
24 if (!visited.has(nums[j])) {
25 visited.add(nums[j]);
26 // nums[j] & 1 is 0 for even numbers, 1 for odd numbers
27 ++count[nums[j] & 1];
28 }
29
30 // If distinct evens equal distinct odds, the subarray is balanced
31 if (count[0] === count[1]) {
32 ans = Math.max(ans, j - i + 1);
33 }
34 }
35 }
36
37 return ans;
38}
39Time and Space Complexity
-
Time complexity:
O(n^2), wherenis the length of the arraynums. The code uses two nested loops: the outer loop iterates over every starting indexi, and the inner loop scans every ending indexjfromiton - 1. Inside the inner loop, all operations — checking membership in the setvis, updating the countercnt, and comparingcnt[0]withcnt[1]— takeO(1)time on average. Therefore, the total work isO(n) * O(n) = O(n^2). -
Space complexity:
O(n), wherenis the length of the arraynums. For each starting indexi, the setvismay store up ton - idistinct elements in the worst case (when all elements in the subarray are unique), requiringO(n)space. The counter arraycntonly uses constant spaceO(1), so the overall space is dominated byvis, givingO(n).
Pattern Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Forgetting to reset seen and count for each new left endpoint
The most frequent mistake is declaring the hash set and counters outside the outer loop:
# WRONG
count = [0, 0]
seen = set()
for left in range(n):
for right in range(left, n):
if nums[right] not in seen:
count[nums[right] & 1] += 1
seen.add(nums[right])
...
Once left advances, the window [left, right] no longer contains the elements before left, but seen still remembers them. This causes genuinely new values to be skipped and counters to drift, producing wrong (usually inflated) answers.
Fix: Re-initialize count = [0, 0] and seen = set() at the start of every iteration of the outer loop, exactly as the correct solution does. Each left endpoint defines a fresh window, so the bookkeeping must start from scratch.
Pitfall 2: Counting occurrences instead of distinct values
A subtle misreading of the problem leads to counting every even/odd element rather than distinct even/odd values:
# WRONG — counts duplicates multiple times
for right in range(left, n):
count[nums[right] & 1] += 1 # no de-duplication
For nums = [2, 2, 3], this incorrectly computes 2 evens vs. 1 odd for the full array and misses the correct answer of 3.
Fix: Guard the increment with a membership check (if nums[right] not in seen) so each value contributes to the counter at most once per window.
Pitfall 3: Attempting to shrink the window with naive removal (broken sliding window)
It is tempting to convert this into a classic two-pointer sliding window and "remove" nums[left] when the left pointer moves:
# WRONG — removal logic is incorrect with duplicates seen.remove(nums[left]) count[nums[left] & 1] -= 1
This fails because the same value may still exist elsewhere in the window. Removing it from seen and decrementing the counter is only valid when its last remaining occurrence leaves the window. Correct removal requires a full frequency map (value → occurrence count), and even then the "balanced" condition is not monotonic, so a standard shrink/grow sliding window does not directly apply.
Fix: Either stick with the enumeration approach (reset per left endpoint, as in the given solution), or if optimizing, maintain a Counter of occurrences and only decrement the distinct counter when a value's frequency drops to zero — while recognizing that the window logic must check balance at every position, not rely on monotonic shrinking.
Pitfall 4: Classifying parity incorrectly for negative numbers with % 2
Using nums[right] % 2 == 1 to test for odd numbers is a trap in some languages. In Python -3 % 2 is 1, so it happens to work, but in C++/Java -3 % 2 is -1, and the check silently misclassifies negative odd numbers.
Fix: Use the bitwise trick nums[right] & 1 (as the solution does) or compare % 2 != 0 instead of == 1. In Python & 1 works uniformly for negatives because of two's-complement semantics on the lowest bit.
Pitfall 5: Updating the answer only when the window grows
Some implementations check the balance condition inside the if nums[right] not in seen block:
# WRONG — balance check skipped for duplicate elements
if nums[right] not in seen:
count[nums[right] & 1] += 1
seen.add(nums[right])
if count[0] == count[1]: # only checked on new values
max_length = max(max_length, right - left + 1)
A duplicate element does not change the distinct counts, but it does extend the window length. For nums = [2, 3, 3], the window [2, 3, 3] is balanced with length 3, yet the check above never fires at right = 2 and reports 2 instead.
Fix: Perform the count[0] == count[1] comparison on every iteration of the inner loop, outside the de-duplication branch.
Ready to land your dream job?
Unlock your dream job with a 5-minute quiz for a personalized study roadmap!
Get My RoadmapConsider the classic dynamic programming of longest increasing subsequence:
Find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order.
For example, the length of LIS for [50, 3, 10, 7, 40, 80] is 4 and LIS is
[3, 7, 40, 80].
What is the recurrence relation?
Recommended Readings
Segment Tree Introduction A segment tree stores information about intervals of an array It supports two operations efficiently update one element and query an aggregate value over a contiguous range In this introduction the aggregate is sum so the operations are update k value and sum arr left right The
https assets algo monster cover_photos dnd svg Divide and Conquer Divide and conquer solves one large problem by repeatedly breaking it into smaller subproblems of the same type solving those subproblems then combining their answers A common template is split solve recursively then merge Merge sort is a classic example
Prefix Sum Technique Explained Prefix Sum A prefix sum transforms an array into a new array of running totals For an input array arr define prefix k as the sum of all elements before index k prefix 0 0 prefix 1 arr 0 prefix 2 arr 0 arr 1 and so on The power
Want a Structured Path to Master System Design Too? Don’t Miss This!