Longest Balanced Subarray II

Hard
Watch on YouTube ↗

Solution

class SegmentTree {
    int n;
    int[] minBalance;   // minimum balance value in this segment
    int[] maxBalance;   // maximum balance value in this segment
    int[] lazyval;      // pending additions not yet propagated to children

    public SegmentTree(int n) {
        this.n = n;
        // Array size is 4*n to accommodate complete binary tree structure
        // Node indexing: parent at i, left child at 2*i, right child at 2*i+1
        minBalance = new int[4 * n];
        maxBalance = new int[4 * n];
        lazyval = new int[4 * n];
    }

    /**
     * Push down pending lazy values from node to its children
     * This applies deferred updates before we read/modify this node
     */
    private void push(int node, int start, int end) {
        if (lazyval[node] != 0) {
            // Apply the pending update to current node's min/max
            minBalance[node] += lazyval[node];
            maxBalance[node] += lazyval[node];

            // If not a leaf, propagate the lazy value to children
            // (defer the actual work to when we visit those children)
            if (start != end) {
                lazyval[node * 2]     += lazyval[node];  // left child
                lazyval[node * 2 + 1] += lazyval[node];  // right child
            }

            // Clear the lazy value at this node (it's been handled)
            lazyval[node] = 0;
        }
    }

    /**
     * Add 'val' to all elements in range [l, r]
     * node: current node index in segment tree
     * [start, end]: range represented by current node
     * [l, r]: range we want to update
     * val: value to add
     */
    public void update(int node, int start, int end,
                            int l, int r, int val) {

        // Apply any pending updates at this node first
        push(node, start, end);

        // Case 1: No overlap - current segment [start,end] doesn't intersect [l,r]
        if (start > r || end < l) return;

        // Case 2: Complete overlap - [start,end] is fully inside [l,r]
        // Mark this node as lazy and return (don't recurse further)
        if (l <= start && end <= r) {
            lazyval[node] += val;
            push(node, start, end);  // immediately apply to this node
            return;
        }

        // Case 3: Partial overlap - need to update both children
        int mid = (start + end) / 2;
        update(node * 2, start, mid, l, r, val);        // update left child
        update(node * 2 + 1, mid + 1, end, l, r, val);  // update right child

        // After updating children, recompute this node's min/max from children
        // This maintains the segment tree property
        minBalance[node] = Math.min(minBalance[node * 2], minBalance[node * 2 + 1]);
        maxBalance[node] = Math.max(maxBalance[node * 2], maxBalance[node * 2 + 1]);
    }

    /**
     * Find the leftmost index where balance == 0
     * Returns -1 if no such index exists
     */
    public int getLeftMost(int node, int start, int end) {
        // Apply any pending updates before checking values
        push(node, start, end);
        
        // Optimization: if entire segment has no zeros, return early
        // - If min > 0, all values are positive (no zero possible)
        // - If max < 0, all values are negative (no zero possible)
        if (minBalance[node] > 0 || maxBalance[node] < 0) {
            return -1;
        }

        // Base case: we've reached a leaf node
        if (start == end) {
            return minBalance[node] == 0 ? start : -1;
        }

        // Recursive case: search children
        int mid = (start + end) / 2;

        // Always search left subtree first (we want leftmost zero)
        int left = getLeftMost(node * 2, start, mid);
        if (left != -1) return left;  // found in left, return immediately

        // Only search right if not found in left
        return getLeftMost(node * 2 + 1, mid + 1, end);
    }
}

class Solution {
    public int longestBalanced(int[] nums) {
        int n = nums.length;

        // Track the last occurrence index of each distinct number
        HashMap<Integer, Integer> prevIndex = new HashMap<>();

        // Segment tree to maintain balance at each position
        // balance[i] = (count of distinct evens from i to end) - (count of distinct odds from i to end)
        SegmentTree st = new SegmentTree(n);

        int maxlen = 0;

        // Process each element left to right
        for(int i = 0; i < n; i++) {
            // Even numbers contribute +1, odd numbers contribute -1
            int val = (nums[i] % 2 == 0) ? 1 : -1;
            
            // If this number appeared before, it's no longer "distinct" at that position
            // Remove its contribution from indices [0, prevIndex]
            if(prevIndex.containsKey(nums[i])) {
                int prev = prevIndex.get(nums[i]);
                st.update(1, 0, n - 1, 0, prev, -val);  // subtract the old contribution
            }
            
            // Add this number's contribution to all indices [0, i]
            // (it's now part of the "distinct" set for subarrays starting at 0..i)
            st.update(1, 0, n - 1, 0, i, val);
            
            // Update the last seen index for this number
            prevIndex.put(nums[i], i);

            // Find leftmost index where balance = 0
            // This means equal distinct evens and odds in subarray [left..i]
            int left = st.getLeftMost(1, 0, n - 1);
            if(left != -1 && left <= i) {
                maxlen = Math.max(maxlen, i - left + 1);
            }
        }

        return maxlen;
    }
}