3755. Find Maximum Balanced XOR Subarray Length
Problem Description
Given an integer array nums, return the length of the longest subarray that satisfies both of the following conditions:
- The bitwise XOR of all elements in the subarray equals
0. - The subarray contains an equal number of even and odd numbers.
If no such subarray exists, return 0.
In other words, you need to find the longest contiguous portion of the array where:
- XOR-ing every element together gives
0, and - The count of even numbers is the same as the count of odd numbers.
The subarray must be non-empty, meaning it contains at least one element. However, since a single element cannot have both an XOR of 0 (unless the element itself is 0, but then it would have one even number and zero odd numbers, breaking the equal-count rule) and an equal number of even and odd numbers, the shortest valid subarray will generally contain an even number of elements.
For example, consider a subarray. To meet the first condition, the XOR of all its values must cancel out to 0. To meet the second condition, exactly half of its elements must be even and the other half must be odd. Only when both conditions hold simultaneously is the subarray considered valid, and among all such valid subarrays, you must report the maximum possible length.
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 first occurrences of combined (prefix XOR, parity balance) states in a hash table finds the longest subarray satisfying both conditions in O(n).
Open in FlowchartIntuition
The two conditions we need to track are both cumulative properties over a contiguous range, which is a strong hint that we can use the prefix sum technique.
Let's analyze each condition separately:
Condition 1: XOR equals 0. If we define a as the prefix XOR (the XOR of all elements from the start up to the current index), then the XOR of a subarray from index i+1 to j equals prefixXor[j] ^ prefixXor[i]. For this subarray XOR to be 0, we need prefixXor[j] ^ prefixXor[i] = 0, which means prefixXor[j] = prefixXor[i]. So the XOR condition is satisfied exactly when two positions share the same prefix XOR value.
Condition 2: equal even and odd counts. Instead of counting evens and odds separately, we can use a single balance value. Let's define b such that we add +1 for every even number and -1 for every odd number. Then a subarray has an equal number of evens and odds precisely when the running balance b is the same at both endpoints, because the +1s and -1s in between cancel out to 0.
Now here is the key insight: both conditions reduce to the same idea — finding two positions where some prefix quantity is identical. So we can combine the two quantities into a single state (a, b), where a is the prefix XOR and b is the even-minus-odd balance.
If the state (a, b) appears at two different positions, then the subarray between them automatically satisfies both conditions at once: the XOR cancels to 0 and the even/odd counts are equal.
To find the longest such subarray, we want the two matching positions to be as far apart as possible. This means for each state, we only care about its first occurrence. By storing the earliest index where each (a, b) state appears in a hash table, whenever we revisit the same state later, we can compute the distance to that earliest position and update our answer.
Finally, we initialize the hash table with the state (0, 0) mapped to index -1. This represents the empty prefix before the array starts, allowing subarrays that begin at index 0 to be correctly measured.
Pattern Learn more about Prefix Sum patterns.
Solution Approach
We use a Prefix Sum + Hash Table approach to efficiently find the longest valid subarray in a single pass.
Data structures used:
- A hash table
dthat maps each state(a, b)to the earliest index at which that state was seen. - Two running variables:
afor the prefix XOR, andbfor the even-minus-odd balance.
Initialization:
We start with d = {(0, 0): -1}. This entry represents the "empty prefix" state before processing any element, where both the prefix XOR and the balance are 0. Mapping it to index -1 ensures that if a valid subarray starts from index 0, its length is computed correctly as i - (-1) = i + 1.
We also set a = b = 0 and ans = 0.
Traversal:
We iterate through the array with both index i and value x using enumerate(nums). For each element:
-
Update the prefix XOR:
a ^= x. This keepsaequal to the XOR of all elements from the start up to and including indexi. -
Update the balance:
b += 1 if x % 2 == 0 else -1. We add+1whenxis even and-1whenxis odd, sobtracks the difference between the count of even and odd numbers seen so far. -
Check the current state
(a, b):- If
(a, b)already exists in the hash tabled, it means the same combined state occurred at an earlier indexd[(a, b)]. The subarray strictly after that earlier position up to the current indexihas an XOR of0and equal even/odd counts. We update the answer withans = max(ans, i - d[(a, b)]). - If
(a, b)does not exist yet, we store it with the current index:d[(a, b)] = i. We only record the first occurrence because keeping the earliest index gives the longest possible span when the state repeats later.
- If
Returning the result:
After processing all elements, we return ans, which holds the length of the longest balanced subarray. If no state ever repeated, ans stays 0, correctly indicating that no valid subarray exists.
Complexity analysis:
- Time complexity:
O(n), wherenis the length ofnums. We make a single pass through the array, and each hash table lookup and insertion takesO(1)on average. - Space complexity:
O(n), since in the worst case the hash table may store up ton + 1distinct states.
Example Walkthrough
Let's trace through the solution using the small example nums = [2, 3, 1, 0].
We want the longest subarray whose XOR is 0 and which has equal even/odd counts. Let's identify the parity of each element first:
2→ even3→ odd1→ odd0→ even
Initialization:
- Hash table:
d = {(0, 0): -1} - Prefix XOR:
a = 0 - Balance (even − odd):
b = 0 - Answer:
ans = 0
We use the convention +1 for even, −1 for odd.
Step i = 0, x = 2 (even):
- Update XOR:
a = 0 ^ 2 = 2 - Update balance:
b = 0 + 1 = 1(even) - Check state
(2, 1):- Not in
d, so record it:d[(2, 1)] = 0 d = {(0,0): -1, (2,1): 0}
- Not in
Step i = 1, x = 3 (odd):
- Update XOR:
a = 2 ^ 3 = 1 - Update balance:
b = 1 - 1 = 0(odd) - Check state
(1, 0):- Not in
d, so record it:d[(1, 0)] = 1 d = {(0,0): -1, (2,1): 0, (1,0): 1}
- Not in
Step i = 2, x = 1 (odd):
- Update XOR:
a = 1 ^ 1 = 0 - Update balance:
b = 0 - 1 = -1(odd) - Check state
(0, -1):- Not in
d, so record it:d[(0, -1)] = 2 d = {(0,0): -1, (2,1): 0, (1,0): 1, (0,-1): 2}
- Not in
Step i = 3, x = 0 (even):
- Update XOR:
a = 0 ^ 0 = 0 - Update balance:
b = -1 + 1 = 0(even) - Check state
(0, 0):- Already in
d! It was first seen at index-1. - This means the subarray from index
0to index3has XOR0and equal even/odd counts. - Update answer:
ans = max(0, 3 - (-1)) = 4
- Already in
Result: ans = 4
Verification of the found subarray [2, 3, 1, 0]:
- XOR check:
2 ^ 3 ^ 1 ^ 0 = 1 ^ 1 ^ 0 = 0 ^ 0 = 0✓ - Parity check: evens are
{2, 0}(count = 2), odds are{3, 1}(count = 2) → equal ✓
Both conditions hold, and the whole array of length 4 is the longest valid subarray. The key moment was at i = 3, where the state (0, 0) matched the initial sentinel entry mapped to -1, letting us span the entire array.
Solution Implementation
1from typing import List
2
3
4class Solution:
5 def maxBalancedSubarray(self, nums: List[int]) -> int:
6 # Map each combined state (prefix_xor, parity_balance) to the
7 # FIRST index at which it occurs. Initialize with the empty-prefix
8 # state at index -1 so subarrays starting from index 0 are valid.
9 first_seen = {(0, 0): -1}
10
11 prefix_xor = 0 # Running XOR of all elements seen so far.
12 parity_balance = 0 # (# of evens) - (# of odds) seen so far.
13 max_length = 0 # Length of the longest balanced subarray.
14
15 for index, value in enumerate(nums):
16 # Update the running XOR; equal XOR at two indices means the
17 # XOR of the subarray between them is 0.
18 prefix_xor ^= value
19
20 # Update the parity balance; equal balance at two indices means
21 # the subarray between them has equal counts of evens and odds.
22 parity_balance += 1 if value % 2 == 0 else -1
23
24 state = (prefix_xor, parity_balance)
25
26 if state in first_seen:
27 # The state repeated: the subarray strictly after the first
28 # occurrence up to the current index satisfies both
29 # conditions. Update the best length found.
30 max_length = max(max_length, index - first_seen[state])
31 else:
32 # Record the first time we encounter this state so future
33 # matches yield the longest possible subarray.
34 first_seen[state] = index
35
36 return max_length
371import java.util.HashMap;
2import java.util.Map;
3
4class Solution {
5 public int maxBalancedSubarray(int[] nums) {
6 // Maps a combined "prefix state" to the earliest index at which it occurs.
7 // The state packs two prefix quantities into one long key.
8 Map<Long, Integer> firstIndexOfState = new HashMap<>();
9
10 int maxLength = 0;
11
12 // prefixXor: running XOR of all elements processed so far.
13 int prefixXor = 0;
14
15 // parityBalance: running count where each even number adds 1 and each odd
16 // number subtracts 1. It starts at nums.length as an offset so the value
17 // always stays non-negative and fits cleanly into the low 32 bits of the key.
18 int parityBalance = nums.length;
19
20 // Register the initial state (before processing any element) at index -1,
21 // so subarrays starting from index 0 can be measured correctly.
22 firstIndexOfState.put((long) parityBalance, -1);
23
24 for (int i = 0; i < nums.length; ++i) {
25 // Update the prefix XOR.
26 prefixXor ^= nums[i];
27
28 // Update the parity balance: +1 for even, -1 for odd.
29 parityBalance += nums[i] % 2 == 0 ? 1 : -1;
30
31 // Pack both quantities into a single long:
32 // high 32 bits -> prefixXor
33 // low 32 bits -> parityBalance
34 long stateKey = (1L * prefixXor << 32) | parityBalance;
35
36 // If this exact state was seen before, the subarray between the previous
37 // occurrence (exclusive) and the current index has a net-zero XOR
38 // contribution and an equal number of even and odd values -> balanced.
39 if (firstIndexOfState.containsKey(stateKey)) {
40 maxLength = Math.max(maxLength, i - firstIndexOfState.get(stateKey));
41 } else {
42 // Record only the first occurrence to maximize subarray length later.
43 firstIndexOfState.put(stateKey, i);
44 }
45 }
46
47 return maxLength;
48 }
49}
501class Solution {
2public:
3 int maxBalancedSubarray(vector<int>& nums) {
4 // Map from a packed state key to the earliest index where it occurred.
5 unordered_map<long long, int> firstIndex;
6 int answer = 0;
7
8 // prefixXor: running XOR of the elements in the prefix.
9 int prefixXor = 0;
10 // parityBalance: running count of (even count - odd count),
11 // initialized with an offset of nums.size() to keep it non-negative.
12 int parityBalance = static_cast<int>(nums.size());
13
14 // Record the base state before processing any element (index -1).
15 firstIndex[static_cast<long long>(parityBalance)] = -1;
16
17 for (int i = 0; i < static_cast<int>(nums.size()); ++i) {
18 // Update the running XOR.
19 prefixXor ^= nums[i];
20 // Even number contributes +1, odd number contributes -1.
21 parityBalance += (nums[i] % 2 == 0) ? 1 : -1;
22
23 // Pack both the XOR value and the parity balance into a single key:
24 // high 32 bits hold prefixXor, low 32 bits hold parityBalance.
25 long long key = (static_cast<long long>(prefixXor) << 32) | static_cast<unsigned int>(parityBalance);
26
27 if (firstIndex.contains(key)) {
28 // The same state seen before means the subarray in between is
29 // balanced (XOR == 0 and equal even/odd counts); update answer.
30 answer = std::max(answer, i - firstIndex[key]);
31 } else {
32 // Store the earliest index for this state.
33 firstIndex[key] = i;
34 }
35 }
36
37 return answer;
38 }
39};
401/**
2 * Finds the length of the longest "balanced" subarray, where a subarray is
3 * balanced when:
4 * 1. The XOR of all its elements equals 0 (equal prefix XOR at both ends).
5 * 2. It contains an equal number of even and odd values (equal parity balance).
6 *
7 * We track two prefix states simultaneously and pack them into a single bigint
8 * key. If the same combined state reappears at indices i and j (i < j), then the
9 * subarray (i, j] satisfies both conditions.
10 *
11 * @param nums - The input array of numbers.
12 * @returns The length of the longest balanced subarray.
13 */
14function maxBalancedSubarray(nums: number[]): number {
15 // Maps a packed (xorPrefix, parityBalance) state to the earliest index it occurred.
16 const stateToIndex = new Map<bigint, number>();
17
18 let maxLength = 0;
19 let xorPrefix = 0; // Running XOR of all elements seen so far.
20
21 // Running parity balance. Offset by nums.length so it never goes negative,
22 // keeping it cleanly within the low 32 bits of the packed key.
23 let parityBalance = nums.length;
24
25 // Record the initial state (before processing any element) at index -1.
26 stateToIndex.set(BigInt(parityBalance), -1);
27
28 for (let i = 0; i < nums.length; ++i) {
29 // Update the prefix XOR with the current element.
30 xorPrefix ^= nums[i];
31
32 // Even numbers add +1, odd numbers add -1 to the balance.
33 parityBalance += nums[i] % 2 === 0 ? 1 : -1;
34
35 // Pack both states into one key: XOR in the high bits, balance in the low bits.
36 const key = (BigInt(xorPrefix) << 32n) | BigInt(parityBalance);
37
38 if (stateToIndex.has(key)) {
39 // Same state seen before -> the subarray in between is balanced.
40 maxLength = Math.max(maxLength, i - stateToIndex.get(key)!);
41 } else {
42 // First occurrence of this state -> store its index.
43 stateToIndex.set(key, i);
44 }
45 }
46
47 return maxLength;
48}
49Time and Space Complexity
Time Complexity
The time complexity is O(n), where n is the length of the array nums.
The reasoning is as follows:
- The code uses a single
forloop that iterates over each element ofnumsexactly once, contributingO(n)iterations. - Inside the loop, each operation is
O(1):a ^= xperforms a constant-time XOR operation to track the running prefix XOR.b += 1 if x % 2 == 0 else -1performs a constant-time parity-balance update.- The dictionary lookup
(a, b) in d, themaxcomparison, and the dictionary insertiond[(a, b)] = iare all averageO(1)operations.
Therefore, the overall time complexity is O(n) × O(1) = O(n).
Space Complexity
The space complexity is O(n), where n is the length of the array nums.
The reasoning is as follows:
- The dictionary
dstores the first occurrence index for each distinct(a, b)state pair. In the worst case, every prefix produces a unique(a, b)key, so up ton + 1entries are stored, requiringO(n)space. - The variables
a,b, andansuse onlyO(1)additional space.
Therefore, the dominant term comes from the dictionary, making the overall space complexity O(n).
Pattern Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall: Overwriting an existing state with a later index
The most common mistake in this prefix-state pattern is storing the index every time you visit a state, instead of only on its first occurrence. For example, writing the loop like this:
for index, value in enumerate(nums):
prefix_xor ^= value
parity_balance += 1 if value % 2 == 0 else -1
state = (prefix_xor, parity_balance)
# BUG: this overwrites the earliest index with a later one
if state in first_seen:
max_length = max(max_length, index - first_seen[state])
first_seen[state] = index # <-- always updating
Why it's wrong: The length of a valid subarray is index - first_seen[state]. To maximize this length, you want first_seen[state] to be as small (early) as possible. If you keep overwriting the stored index with the current (larger) one, the difference index - first_seen[state] shrinks, and you will under-report the true maximum length.
Concrete failure example: Suppose the same state (a, b) appears at indices 1, 3, and 7.
- Correct behavior: keep index
1, so when you reach index7you compute length7 - 1 = 6. - Buggy behavior: at index
3you overwrite with3, then at index7you compute only7 - 3 = 4, missing the longer span.
Solution: Guard the insertion with else (or an explicit if state not in first_seen) so the index is recorded only once — on the first encounter — which is exactly what the correct code does:
if state in first_seen:
max_length = max(max_length, index - first_seen[state])
else:
first_seen[state] = index # record ONLY the first occurrence
Pitfall: Forgetting the (0, 0): -1 sentinel
Another frequent error is initializing the hash table as empty (first_seen = {}) instead of {(0, 0): -1}.
Why it's wrong: Without the sentinel, a valid subarray that starts at index 0 can never be detected, because the "empty prefix" state (XOR = 0, balance = 0) was never recorded. You would miss any answer that spans from the very beginning of the array.
Concrete failure example: For nums = [2, 3] (one even, one odd, 2 ^ 3 = 1)... this particular case has no valid subarray, but consider nums = [3, 3]: 3 ^ 3 = 0, but both are odd, so balance is -2, not valid. A cleaner example is nums = [2, 2, 3, 3] where the whole array has XOR 0 and 2 evens / 2 odds. The full-array state is (0, 0). Without the sentinel mapping (0, 0) to -1, when you reach the last index the state (0, 0) would only match an internal earlier occurrence (or none), and you'd fail to report the full length 4 = 3 - (-1).
Solution: Always seed the map with the empty-prefix state:
first_seen = {(0, 0): -1}
This ensures subarrays beginning at index 0 are measured correctly as index - (-1) = index + 1.
Pitfall: Using a mutable or unhashable key
Combining the two running quantities into a list ([prefix_xor, parity_balance]) instead of a tuple will raise TypeError: unhashable type: 'list' when used as a dictionary key.
Solution: Always pack the combined state as an immutable tuple (prefix_xor, parity_balance), which is hashable and safe to use as a key.
Ready to land your dream job?
Unlock your dream job with a 5-minute quiz for a personalized study roadmap!
Get My RoadmapWhich type of traversal does breadth first search do?
Recommended Readings
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
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion If you prefer videos here's a video that explains recursion in a fun and easy way Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask him
Want a Structured Path to Master System Design Too? Don’t Miss This!