Skip to main content
Back to problems
#3762
Hard Algorithms

Minimum operations to equalize subarrays

Array Math Binary Search Segment Tree
19.5% acceptance
Feb 25, 2026
49
3
You are given an integer array nums and an integer k. In one operation, you can increase or decrease any element of nums by exactly k. You are also given a 2D integer array queries, where each queries[i] = [li, ri]. For each query, find the minimum number of operations required to make all elements in the subarray nums[li..ri] equal. If it is impossible, the answer for that query is -1. Return an array ans, where ans[i] is the answer for the ith query.

Solution

Rust
Time O(n * m)
Space O(log(1e9)
LeetCode
solution.rs
impl Solution {
  pub fn min_operations(nums: Vec<i32>, k: i32, queries: Vec<Vec<i32>>) -> Vec<i64> {
    let n = nums.len();
    let k64 = k as i64;

    // Prefix sums of nums values for O(1) range sum.
    let mut prefix_sum = vec![0i64; n + 1];
    for i in 0..n {
      prefix_sum[i + 1] = prefix_sum[i] + nums[i] as i64;
    }

    // break_sum[i] = # of boundary positions j in [1..i] where nums[j]%k != nums[j-1]%k.
    // A query [l,r] is consistent iff break_sum[r] == break_sum[l].
    let mut break_sum = vec![0i32; n];
    for i in 1..n {
      break_sum[i] = break_sum[i - 1] + if nums[i] % k != nums[i - 1] % k { 1 } else { 0 };
    }

    // --- Merge Sort Tree (segment tree with sorted arrays + prefix sums) ---
    // Supports range (count_le, sum_le) queries in O(log n) each.
    // Binary-searching for the median over values takes O(log(MAX_VAL) * log n).
    let size = n.next_power_of_two();
    // tree[node] = sorted slice of nums values for that node's range.
    // psum[node][j] = prefix sum of tree[node][0..j].
    let mut tree: Vec<Vec<i32>> = vec![vec![]; 2 * size];
    let mut psum: Vec<Vec<i64>> = vec![vec![]; 2 * size];

    for i in 0..n {
      tree[size + i] = vec![nums[i]];
      psum[size + i] = vec![0, nums[i] as i64];
    }
    for node in (1..size).rev() {
      let (l, r) = (2 * node, 2 * node + 1);
      // Merge two sorted arrays.
      let mut merged = Vec::with_capacity(tree[l].len() + tree[r].len());
      let (mut li, mut ri) = (0, 0);
      while li < tree[l].len() && ri < tree[r].len() {
        if tree[l][li] <= tree[r][ri] {
          merged.push(tree[l][li]);
          li += 1;
        } else {
          merged.push(tree[r][ri]);
          ri += 1;
        }
      }
      merged.extend_from_slice(&tree[l][li..]);
      merged.extend_from_slice(&tree[r][ri..]);
      let mut ps = vec![0i64; merged.len() + 1];
      for (j, &v) in merged.iter().enumerate() {
        ps[j + 1] = ps[j] + v as i64;
      }
      tree[node] = merged;
      psum[node] = ps;
    }

    // Returns (count of elements <= val, their sum) in nums[ql..=qr].
    let query_cs = |ql: usize, qr: usize, val: i32| -> (i64, i64) {
      let mut lo = ql + size;
      let mut hi = qr + size + 1;
      let mut cnt = 0i64;
      let mut sum = 0i64;
      while lo < hi {
        if lo & 1 == 1 {
          let pos = tree[lo].partition_point(|&x| x <= val);
          cnt += pos as i64;
          sum += psum[lo][pos];
          lo += 1;
        }
        if hi & 1 == 1 {
          hi -= 1;
          let pos = tree[hi].partition_point(|&x| x <= val);
          cnt += pos as i64;
          sum += psum[hi][pos];
        }
        lo >>= 1;
        hi >>= 1;
      }
      (cnt, sum)
    };

    queries
      .iter()
      .map(|q| {
        let l = q[0] as usize;
        let r = q[1] as usize;

        // O(1) consistency check.
        if l < r && break_sum[r] != break_sum[l] {
          return -1i64;
        }

        let len = (r - l + 1) as i64;
        let total_sum = prefix_sum[r + 1] - prefix_sum[l];
        // We want the lower median: smallest value v s.t. count_le(v) >= ceil(len/2).
        let target = (len + 1) / 2;

        // Binary search on value space: O(log(1e9) * log n) ≈ 30 * log n.
        let median = {
          let mut lo = 1i32;
          let mut hi = 1_000_000_000i32;
          while lo < hi {
            let mid = lo + (hi - lo) / 2;
            let (cnt, _) = query_cs(l, r, mid);
            if cnt >= target {
              hi = mid;
            } else {
              lo = mid + 1;
            }
          }
          lo
        };

        // sum |x - median| for x in nums[l..=r].
        // = median * cnt_le - sum_le  +  (total_sum - sum_le) - median * (len - cnt_le)
        let (cnt_le, sum_le) = query_cs(l, r, median);
        let cost = median as i64 * cnt_le - sum_le
          + (total_sum - sum_le) - median as i64 * (len - cnt_le);

        // Divide by k (exact because all residues are equal).
        cost / k64
      })
      .collect()
  }
}