Skip to main content
Back to problems
#3454
Hard Algorithms

Separate squares ii

Array Binary Search Segment Tree Sweep Line
59.9% acceptance
Feb 25, 2026
272
65
You are given a 2D integer array squares. Each squares[i] = [xi, yi, li] represents the coordinates of the bottom-left point and the side length of a square parallel to the x-axis. Find the minimum y-coordinate value of a horizontal line such that the total area covered by squares above the line equals the total area covered by squares below the line. Answers within 10-5 of the actual answer will be accepted. Note: Squares may overlap. Overlapping areas should be counted only once in this version.

Solution

Rust
Time O(n²)
Space O(n)
LeetCode
solution.rs
// Segment tree for range-add / covered-length query.
// Leaves represent x-intervals [xs[i], xs[i+1]]; cnt[node] counts how many
// times the node's full range is directly covered by an open rectangle.
// len[node] = total covered length in the node's range.
struct SegTree {
  xs: Vec<i64>,
  cnt: Vec<i32>,
  len: Vec<i64>,
}

impl SegTree {
  fn new(xs: Vec<i64>) -> Self {
    let m = xs.len();
    SegTree { xs, cnt: vec![0; 4 * m], len: vec![0; 4 * m] }
  }

  fn push_up(&mut self, node: usize, lo: usize, hi: usize) {
    if self.cnt[node] > 0 {
      self.len[node] = self.xs[hi] - self.xs[lo];
    } else if hi - lo == 1 {
      self.len[node] = 0;
    } else {
      self.len[node] = self.len[2 * node] + self.len[2 * node + 1];
    }
  }

  // Add `delta` to every leaf interval that lies within [xs[l], xs[r])
  fn update(&mut self, node: usize, lo: usize, hi: usize, l: usize, r: usize, delta: i32) {
    if r <= lo || hi <= l { return; }
    if l <= lo && hi <= r {
      self.cnt[node] += delta;
      self.push_up(node, lo, hi);
      return;
    }
    let mid = (lo + hi) / 2;
    self.update(2 * node, lo, mid, l, r, delta);
    self.update(2 * node + 1, mid, hi, l, r, delta);
    self.push_up(node, lo, hi);
  }
}

impl Solution {
  pub fn separate_squares(squares: Vec<Vec<i32>>) -> f64 {
    // 1. Compress x-coordinates.
    let mut xs: Vec<i64> = squares
      .iter()
      .flat_map(|s| [s[0] as i64, s[0] as i64 + s[2] as i64])
      .collect();
    xs.sort_unstable();
    xs.dedup();
    let m = xs.len();
    if m < 2 { return 0.0; }
    let n_intervals = m - 1;

    // 2. Build events: (y, delta, x_left_idx, x_right_idx)
    //    +1 when a square starts, -1 when it ends.
    let mut events: Vec<(i64, i32, usize, usize)> = Vec::with_capacity(squares.len() * 2);
    for s in &squares {
      let xi = s[0] as i64;
      let yi = s[1] as i64;
      let li = s[2] as i64;
      let il = xs.partition_point(|&v| v < xi);
      let ir = xs.partition_point(|&v| v < xi + li);
      events.push((yi,      1, il, ir));
      events.push((yi + li, -1, il, ir));
    }
    // Sort by y; at the same y process additions (+1) before removals (-1)
    // so that a square ending exactly where another starts is handled correctly.
    events.sort_unstable_by(|a, b| a.0.cmp(&b.0).then(b.1.cmp(&a.1)));

    // 3. One-pass sweep: accumulate (y_start, dy, covered_x_width) strips.
    let mut seg = SegTree::new(xs);
    let mut strips: Vec<(i64, i64, i64)> = Vec::new(); // (y_start, dy, width)

    let mut ei = 0;
    let ne = events.len();
    let mut prev_y = events[0].0;

    while ei < ne {
      let cur_y = events[ei].0;
      if cur_y > prev_y {
        strips.push((prev_y, cur_y - prev_y, seg.len[1]));
      }
      prev_y = cur_y;
      while ei < ne && events[ei].0 == cur_y {
        let (_, delta, il, ir) = events[ei];
        seg.update(1, 0, n_intervals, il, ir, delta);
        ei += 1;
      }
    }

    // 4. Find the y that splits total area in half.
    //    Work in integer arithmetic (×2) to avoid precision loss until the final step.
    let total: i128 = strips.iter().map(|&(_, dy, w)| dy as i128 * w as i128).sum();
    let half_x2 = total; // 2 * half

    let mut cum2: i128 = 0;
    for &(y_start, dy, width) in &strips {
      let area2 = 2 * dy as i128 * width as i128;
      if cum2 + area2 >= half_x2 {
        if width == 0 {
          return y_start as f64;
        }
        // y_start + (half - cum) / width
        // = y_start + (half_x2 - cum2) / (2 * width)
        let remaining = (half_x2 - cum2) as f64;
        return y_start as f64 + remaining / (2.0 * width as f64);
      }
      cum2 += area2;
    }

    // Fallback (unreachable for valid input)
    prev_y as f64
  }
}