A subarray is called balanced if the number of distinct even numbers in the subarray is equal to the number of distinct odd numbers.
Return the length of the longest balanced subarray.
Β
Example 1:
Input:nums = [2,5,4,3]
Output:4
Explanation:
The longest balanced subarray is [2, 5, 4, 3].
It has 2 distinct even numbers [2, 4] and 2 distinct odd numbers [5, 3]. Thus, the answer is 4.
Example 2:
Input:nums = [3,2,2,5,4]
Output:5
Explanation:
The longest balanced subarray is [3, 2, 2, 5, 4].
It has 2 distinct even numbers [2, 4] and 2 distinct odd numbers [3, 5]. Thus, the answer is 5.
Example 3:
Input:nums = [1,2,3,2]
Output:3
Explanation:
The longest balanced subarray is [2, 3, 2].
It has 1 distinct even number [2] and 1 distinct odd number [3]. Thus, the answer is 3.
Β
Constraints:
1 <= nums.length <= 105
1 <= nums[i] <= 105
Solutions
Solution 1: Segment Tree + Prefix Sum + Hash Table
We can transform the problem into a prefix sum problem. Define a prefix sum variable \(\textit{now}\), representing the difference between odd and even numbers in the current subarray:
For odd elements, record as \(+1\), and for even elements, record as \(-1\). Use a hash table \(\textit{last}\) to record the last occurrence position of each number. If a number appears repeatedly, we need to revoke its previous contribution.
To efficiently calculate the subarray length each time a right endpoint element is added, we use a segment tree to maintain the minimum and maximum values of the interval prefix sum, while supporting interval addition operations and binary search queries on the segment tree. When iterating to right endpoint \(i\), first update the contribution of the current element, then use the segment tree to query the earliest position \(pos\) where the current prefix sum \(\textit{now}\) appears. The current subarray length is \(i - pos\), and we update the answer:
\[ \textit{ans} = \max(\textit{ans}, i - pos) \]
The time complexity is \(O(n \log n)\), where \(n\) is the length of the array. Each segment tree modification and query operation takes \(O(\log n)\), and enumerating the right endpoint \(n\) times gives a total time complexity of \(O(n \log n)\). The space complexity is \(O(n)\), where segment tree nodes and the hash table each occupy \(O(n)\) space.
/** * * Idea: * - Treat each distinct odd number as +1, and each distinct even number as -1 * - Maintain prefix sums representing the balance * - When a number appears again, remove its previous contribution * - Use a segment tree to maintain min/max prefix sums with range add * - Binary search on the segment tree to find the earliest equal prefix sum */classSolution{/** * Segment tree node */staticclassNode{intl,r;// segment rangeintmn,mx;// minimum / maximum prefix sumintlazy;// lazy propagation (range add)}/** * Segment tree supporting: * - range add * - find the smallest index with a given prefix sum */staticclassSegmentTree{Node[]tr;SegmentTree(intn){tr=newNode[n<<2];for(inti=0;i<tr.length;i++){tr[i]=newNode();}build(1,0,n);}// Build an empty tree with all prefix sums = 0voidbuild(intu,intl,intr){tr[u].l=l;tr[u].r=r;tr[u].mn=tr[u].mx=0;tr[u].lazy=0;if(l==r)return;intmid=(l+r)>>1;build(u<<1,l,mid);build(u<<1|1,mid+1,r);}// Add value v to all prefix sums in [l, r]voidmodify(intu,intl,intr,intv){if(tr[u].l>=l&&tr[u].r<=r){apply(u,v);return;}pushdown(u);intmid=(tr[u].l+tr[u].r)>>1;if(l<=mid)modify(u<<1,l,r,v);if(r>mid)modify(u<<1|1,l,r,v);pushup(u);}// Binary search on the segment tree// Find the smallest index where prefix sum == targetintquery(intu,inttarget){if(tr[u].l==tr[u].r){returntr[u].l;}pushdown(u);intleft=u<<1;intright=u<<1|1;if(tr[left].mn<=target&&target<=tr[left].mx){returnquery(left,target);}returnquery(right,target);}// Apply range addvoidapply(intu,intv){tr[u].mn+=v;tr[u].mx+=v;tr[u].lazy+=v;}// Update from childrenvoidpushup(intu){tr[u].mn=Math.min(tr[u<<1].mn,tr[u<<1|1].mn);tr[u].mx=Math.max(tr[u<<1].mx,tr[u<<1|1].mx);}// Push lazy tag downvoidpushdown(intu){if(tr[u].lazy!=0){apply(u<<1,tr[u].lazy);apply(u<<1|1,tr[u].lazy);tr[u].lazy=0;}}}publicintlongestBalanced(int[]nums){intn=nums.length;SegmentTreest=newSegmentTree(n);// last[x] = last position where value x appearedMap<Integer,Integer>last=newHashMap<>();intnow=0;// current prefix sumintans=0;// answer// Enumerate the right endpointfor(inti=1;i<=n;i++){intx=nums[i-1];intdet=(x&1)==1?1:-1;// Remove previous contribution if x appeared beforeif(last.containsKey(x)){st.modify(1,last.get(x),n,-det);now-=det;}// Add current contributionlast.put(x,i);st.modify(1,i,n,det);now+=det;// Find earliest position with the same prefix sumintpos=st.query(1,now);ans=Math.max(ans,i-pos);}returnans;}}
/* * Segment tree node. * It maintains: * - [l, r] : the covered index range * - mn : minimum prefix sum in this range * - mx : maximum prefix sum in this range * - lazy : lazy propagation value (range add) */classNode{public:intl=0,r=0;intmn=0,mx=0;intlazy=0;};/* * Segment Tree that supports: * 1. Range add * 2. Query the smallest index whose prefix sum equals a given value */classSegmentTree{public:SegmentTree(intn){tr.resize(n<<2);for(inti=0;i<(int)tr.size();++i){tr[i]=newNode();}// Build the tree on range [0, n]build(1,0,n);}/* * Add value v to all prefix sums in range [l, r] */voidmodify(intu,intl,intr,intv){if(tr[u]->l>=l&&tr[u]->r<=r){apply(u,v);return;}pushdown(u);intmid=(tr[u]->l+tr[u]->r)>>1;if(l<=mid)modify(u<<1,l,r,v);if(r>mid)modify(u<<1|1,l,r,v);pushup(u);}/* * Binary search on the segment tree. * Find the smallest index pos such that prefix_sum[pos] == target. * * The key observation: * If target is within [mn, mx] of a segment, then such a position * must exist inside this segment. */intquery(intu,inttarget){if(tr[u]->l==tr[u]->r){returntr[u]->l;}pushdown(u);intlc=u<<1,rc=u<<1|1;if(tr[lc]->mn<=target&&target<=tr[lc]->mx){returnquery(lc,target);}returnquery(rc,target);}private:vector<Node*>tr;/* * Build an empty segment tree. * Initial prefix sums are all zero. */voidbuild(intu,intl,intr){tr[u]->l=l;tr[u]->r=r;tr[u]->mn=tr[u]->mx=0;tr[u]->lazy=0;if(l==r)return;intmid=(l+r)>>1;build(u<<1,l,mid);build(u<<1|1,mid+1,r);}/* * Apply a range add to node u. */voidapply(intu,intv){tr[u]->mn+=v;tr[u]->mx+=v;tr[u]->lazy+=v;}/* * Push information from children to parent. */voidpushup(intu){tr[u]->mn=min(tr[u<<1]->mn,tr[u<<1|1]->mn);tr[u]->mx=max(tr[u<<1]->mx,tr[u<<1|1]->mx);}/* * Push lazy tag down to children. */voidpushdown(intu){if(tr[u]->lazy!=0){apply(u<<1,tr[u]->lazy);apply(u<<1|1,tr[u]->lazy);tr[u]->lazy=0;}}};classSolution{public:intlongestBalanced(vector<int>&nums){intn=nums.size();SegmentTreest(n);/* * last[x] = the most recent position where value x appeared */unordered_map<int,int>last;intnow=0;// current prefix sumintans=0;// answer/* * Enumerate the right endpoint of the subarray */for(inti=1;i<=n;++i){intx=nums[i-1];/* * Contribution of x: * +1 if x is odd * -1 if x is even */intdet=(x&1)?1:-1;/* * If x appeared before, remove its previous contribution */if(last.count(x)){st.modify(1,last[x],n,-det);now-=det;}/* * Add current contribution of x */last[x]=i;st.modify(1,i,n,det);now+=det;/* * Find the smallest position pos such that * prefix_sum[pos] == now */intpos=st.query(1,now);/* * Update the maximum balanced subarray length */ans=max(ans,i-pos);}returnans;}};
// Segment tree nodetypeNodestruct{l,rint// segment rangemn,mxint// minimum / maximum prefix sumlazyint// lazy propagation (range add)}// Segment treetypeSegmentTreestruct{tr[]Node}// Build a segment tree for range [0, n]funcNewSegmentTree(nint)*SegmentTree{st:=&SegmentTree{tr:make([]Node,n<<2),}st.build(1,0,n)returnst}// Initialize all prefix sums to 0func(st*SegmentTree)build(u,l,rint){st.tr[u]=Node{l:l,r:r,mn:0,mx:0,lazy:0}ifl==r{return}mid:=(l+r)>>1st.build(u<<1,l,mid)st.build(u<<1|1,mid+1,r)}// Add v to all prefix sums in [l, r]func(st*SegmentTree)modify(u,l,r,vint){ifst.tr[u].l>=l&&st.tr[u].r<=r{st.apply(u,v)return}st.pushdown(u)mid:=(st.tr[u].l+st.tr[u].r)>>1ifl<=mid{st.modify(u<<1,l,r,v)}ifr>mid{st.modify(u<<1|1,l,r,v)}st.pushup(u)}// Binary search on the segment tree// Find the smallest index where prefix sum == targetfunc(st*SegmentTree)query(u,targetint)int{ifst.tr[u].l==st.tr[u].r{returnst.tr[u].l}st.pushdown(u)left,right:=u<<1,u<<1|1ifst.tr[left].mn<=target&&target<=st.tr[left].mx{returnst.query(left,target)}returnst.query(right,target)}// Apply range additionfunc(st*SegmentTree)apply(u,vint){st.tr[u].mn+=vst.tr[u].mx+=vst.tr[u].lazy+=v}// Update from childrenfunc(st*SegmentTree)pushup(uint){st.tr[u].mn=min(st.tr[u<<1].mn,st.tr[u<<1|1].mn)st.tr[u].mx=max(st.tr[u<<1].mx,st.tr[u<<1|1].mx)}// Push lazy value downfunc(st*SegmentTree)pushdown(uint){ifst.tr[u].lazy!=0{v:=st.tr[u].lazyst.apply(u<<1,v)st.apply(u<<1|1,v)st.tr[u].lazy=0}}// Main functionfunclongestBalanced(nums[]int)int{n:=len(nums)st:=NewSegmentTree(n)// last[x] = last position where value x appearedlast:=make(map[int]int)now:=0// current prefix sumans:=0// answer// Enumerate right endpointfori:=1;i<=n;i++{x:=nums[i-1]det:=-1ifx&1==1{det=1}// Remove previous contribution if x appeared beforeifpos,ok:=last[x];ok{st.modify(1,pos,n,-det)now-=det}// Add current contributionlast[x]=ist.modify(1,i,n,det)now+=det// Find earliest position with same prefix sumpos:=st.query(1,now)ans=max(ans,i-pos)}returnans}