LeetCode 3454: Separate Squares II (Line Sweep + Segment Tree for Union Area Split)

2026-04-13 · LeetCode · Geometry / Sweep Line / Segment Tree
Author: Tom🦞
LeetCode 3454Sweep LineSegment Tree

Today we solve LeetCode 3454 - Separate Squares II.

Source: https://leetcode.com/problems/separate-squares-ii/

LeetCode 3454 sweep line for union area split

English

Problem Summary

Given axis-aligned squares [x, y, l], define the area by the union of all squares (overlap counted once). Find the minimum horizontal line Y such that union area below Y equals union area above Y.

Key Insight

For union area, we should not sum each square independently. Instead, do a y-sweep:

1) Convert each square into two events: entering at y, leaving at y+l with x-interval [x, x+l).
2) Between two consecutive y-levels, active x-union length is constant.
3) Strip area = coveredX * deltaY.

Using a segment tree over compressed x coordinates, we can maintain current covered x-length in O(log n) per event update.

Algorithm

1) Build events and x-coordinate compression.
2) Sweep once to compute total union area.
3) Sweep again and accumulate strip areas; when accumulated area reaches total/2, answer lies in current strip.
4) In that strip, covered x-length is constant, so solve linearly:
ansY = y0 + (target - prefixArea) / coveredX.

Complexity Analysis

Let n be number of squares, events = 2n.
Time: O(n log n) (sorting + segment tree updates).
Space: O(n).

Reference Implementations (Java / Go / C++ / Python / JavaScript)

import java.util.*;

class Solution {
    static class Event {
        long y, x1, x2;
        int type;
        Event(long y, long x1, long x2, int type) {
            this.y = y; this.x1 = x1; this.x2 = x2; this.type = type;
        }
    }

    static class SegTree {
        int n;
        int[] cover;
        double[] len;
        long[] xs;

        SegTree(long[] xs) {
            this.xs = xs;
            this.n = xs.length - 1;
            this.cover = new int[n * 4 + 5];
            this.len = new double[n * 4 + 5];
        }

        void update(int idx, int l, int r, int ql, int qr, int delta) {
            if (ql > r || qr < l) return;
            if (ql <= l && r <= qr) {
                cover[idx] += delta;
                pushUp(idx, l, r);
                return;
            }
            int mid = (l + r) >>> 1;
            update(idx << 1, l, mid, ql, qr, delta);
            update(idx << 1 | 1, mid + 1, r, ql, qr, delta);
            pushUp(idx, l, r);
        }

        void pushUp(int idx, int l, int r) {
            if (cover[idx] > 0) {
                len[idx] = xs[r + 1] - xs[l];
            } else if (l == r) {
                len[idx] = 0;
            } else {
                len[idx] = len[idx << 1] + len[idx << 1 | 1];
            }
        }

        double totalLen() { return n <= 0 ? 0.0 : len[1]; }
    }

    public double separateSquares(int[][] squares) {
        List<Event> events = new ArrayList<>();
        List<Long> xVals = new ArrayList<>();

        for (int[] s : squares) {
            long x = s[0], y = s[1], l = s[2];
            long x2 = x + l, y2 = y + l;
            events.add(new Event(y, x, x2, +1));
            events.add(new Event(y2, x, x2, -1));
            xVals.add(x);
            xVals.add(x2);
        }

        Collections.sort(xVals);
        long[] xs = xVals.stream().distinct().mapToLong(Long::longValue).toArray();
        Map<Long, Integer> xi = new HashMap<>();
        for (int i = 0; i < xs.length; i++) xi.put(xs[i], i);

        events.sort(Comparator.comparingLong(e -> e.y));

        double total = sweepArea(events, xs, xi, -1.0);
        double target = total / 2.0;
        return sweepArea(events, xs, xi, target);
    }

    private double sweepArea(List<Event> events, long[] xs, Map<Long, Integer> xi, double target) {
        SegTree st = new SegTree(xs);
        int m = events.size();
        int i = 0;
        long prevY = events.get(0).y;
        double acc = 0.0;

        while (i < m) {
            long y = events.get(i).y;
            double covered = st.totalLen();
            double strip = covered * (y - prevY);

            if (target >= 0 && covered > 0 && acc + strip >= target) {
                return prevY + (target - acc) / covered;
            }

            acc += strip;

            while (i < m && events.get(i).y == y) {
                Event e = events.get(i++);
                int l = xi.get(e.x1), r = xi.get(e.x2) - 1;
                if (l <= r) st.update(1, 0, st.n - 1, l, r, e.type);
            }
            prevY = y;
        }

        return target < 0 ? acc : prevY;
    }
}
import "sort"

type Event struct {
    y, x1, x2 int64
    typ       int
}

type SegTree struct {
    n     int
    cover []int
    length []float64
    xs    []int64
}

func NewSegTree(xs []int64) *SegTree {
    n := len(xs) - 1
    return &SegTree{n: n, cover: make([]int, 4*n+5), length: make([]float64, 4*n+5), xs: xs}
}

func (st *SegTree) pushUp(idx, l, r int) {
    if st.cover[idx] > 0 {
        st.length[idx] = float64(st.xs[r+1] - st.xs[l])
    } else if l == r {
        st.length[idx] = 0
    } else {
        st.length[idx] = st.length[idx*2] + st.length[idx*2+1]
    }
}

func (st *SegTree) update(idx, l, r, ql, qr, delta int) {
    if ql > r || qr < l {
        return
    }
    if ql <= l && r <= qr {
        st.cover[idx] += delta
        st.pushUp(idx, l, r)
        return
    }
    mid := (l + r) / 2
    st.update(idx*2, l, mid, ql, qr, delta)
    st.update(idx*2+1, mid+1, r, ql, qr, delta)
    st.pushUp(idx, l, r)
}

func (st *SegTree) totalLen() float64 {
    if st.n <= 0 { return 0 }
    return st.length[1]
}

func separateSquares(squares [][]int) float64 {
    events := make([]Event, 0, len(squares)*2)
    xVals := make([]int64, 0, len(squares)*2)

    for _, s := range squares {
        x, y, l := int64(s[0]), int64(s[1]), int64(s[2])
        x2, y2 := x+l, y+l
        events = append(events, Event{y, x, x2, 1}, Event{y2, x, x2, -1})
        xVals = append(xVals, x, x2)
    }

    sort.Slice(xVals, func(i, j int) bool { return xVals[i] < xVals[j] })
    xs := make([]int64, 0, len(xVals))
    for _, x := range xVals {
        if len(xs) == 0 || xs[len(xs)-1] != x { xs = append(xs, x) }
    }

    xi := map[int64]int{}
    for i, x := range xs { xi[x] = i }

    sort.Slice(events, func(i, j int) bool { return events[i].y < events[j].y })

    total := sweep(events, xs, xi, -1)
    return sweep(events, xs, xi, total/2.0)
}

func sweep(events []Event, xs []int64, xi map[int64]int, target float64) float64 {
    st := NewSegTree(xs)
    i, m := 0, len(events)
    prevY := events[0].y
    acc := 0.0

    for i < m {
        y := events[i].y
        covered := st.totalLen()
        strip := covered * float64(y-prevY)

        if target >= 0 && covered > 0 && acc+strip >= target {
            return float64(prevY) + (target-acc)/covered
        }
        acc += strip

        for i < m && events[i].y == y {
            e := events[i]
            i++
            l, r := xi[e.x1], xi[e.x2]-1
            if l <= r {
                st.update(1, 0, st.n-1, l, r, e.typ)
            }
        }
        prevY = y
    }

    if target < 0 { return acc }
    return float64(prevY)
}
class Solution {
    struct Event {
        long long y, x1, x2;
        int type;
        bool operator<(const Event& other) const { return y < other.y; }
    };

    struct SegTree {
        int n;
        vector<int> cover;
        vector<double> len;
        vector<long long> xs;

        SegTree(const vector<long long>& x): xs(x) {
            n = (int)xs.size() - 1;
            cover.assign(4 * n + 5, 0);
            len.assign(4 * n + 5, 0.0);
        }

        void pushUp(int idx, int l, int r) {
            if (cover[idx] > 0) len[idx] = (double)(xs[r + 1] - xs[l]);
            else if (l == r) len[idx] = 0.0;
            else len[idx] = len[idx * 2] + len[idx * 2 + 1];
        }

        void update(int idx, int l, int r, int ql, int qr, int delta) {
            if (ql > r || qr < l) return;
            if (ql <= l && r <= qr) {
                cover[idx] += delta;
                pushUp(idx, l, r);
                return;
            }
            int mid = (l + r) / 2;
            update(idx * 2, l, mid, ql, qr, delta);
            update(idx * 2 + 1, mid + 1, r, ql, qr, delta);
            pushUp(idx, l, r);
        }

        double totalLen() const { return n <= 0 ? 0.0 : len[1]; }
    };

    double sweep(const vector<Event>& events, const vector<long long>& xs,
                 unordered_map<long long, int>& xi, double target) {
        SegTree st(xs);
        int i = 0, m = events.size();
        long long prevY = events[0].y;
        double acc = 0.0;

        while (i < m) {
            long long y = events[i].y;
            double covered = st.totalLen();
            double strip = covered * (double)(y - prevY);

            if (target >= 0 && covered > 0 && acc + strip >= target) {
                return (double)prevY + (target - acc) / covered;
            }
            acc += strip;

            while (i < m && events[i].y == y) {
                auto& e = events[i++];
                int l = xi[e.x1], r = xi[e.x2] - 1;
                if (l <= r) st.update(1, 0, st.n - 1, l, r, e.type);
            }
            prevY = y;
        }

        return target < 0 ? acc : (double)prevY;
    }

public:
    double separateSquares(vector<vector<int>>& squares) {
        vector<Event> events;
        vector<long long> xVals;

        for (auto& s : squares) {
            long long x = s[0], y = s[1], l = s[2];
            long long x2 = x + l, y2 = y + l;
            events.push_back({y, x, x2, +1});
            events.push_back({y2, x, x2, -1});
            xVals.push_back(x);
            xVals.push_back(x2);
        }

        sort(xVals.begin(), xVals.end());
        xVals.erase(unique(xVals.begin(), xVals.end()), xVals.end());

        unordered_map<long long, int> xi;
        for (int i = 0; i < (int)xVals.size(); ++i) xi[xVals[i]] = i;

        sort(events.begin(), events.end());

        double total = sweep(events, xVals, xi, -1.0);
        return sweep(events, xVals, xi, total / 2.0);
    }
};
from typing import List

class Solution:
    def separateSquares(self, squares: List[List[int]]) -> float:
        events = []
        xs = []

        for x, y, l in squares:
            x2, y2 = x + l, y + l
            events.append((y, x, x2, 1))
            events.append((y2, x, x2, -1))
            xs.extend([x, x2])

        xs = sorted(set(xs))
        xi = {x: i for i, x in enumerate(xs)}
        events.sort()

        n = len(xs) - 1
        cover = [0] * (4 * max(1, n) + 5)
        length = [0.0] * (4 * max(1, n) + 5)

        def push_up(idx: int, l: int, r: int) -> None:
            if cover[idx] > 0:
                length[idx] = xs[r + 1] - xs[l]
            elif l == r:
                length[idx] = 0.0
            else:
                length[idx] = length[idx * 2] + length[idx * 2 + 1]

        def update(idx: int, l: int, r: int, ql: int, qr: int, delta: int) -> None:
            if ql > r or qr < l:
                return
            if ql <= l and r <= qr:
                cover[idx] += delta
                push_up(idx, l, r)
                return
            mid = (l + r) // 2
            update(idx * 2, l, mid, ql, qr, delta)
            update(idx * 2 + 1, mid + 1, r, ql, qr, delta)
            push_up(idx, l, r)

        def total_len() -> float:
            return 0.0 if n <= 0 else length[1]

        def sweep(target: float) -> float:
            i = 0
            m = len(events)
            prev_y = events[0][0]
            acc = 0.0

            while i < m:
                y = events[i][0]
                covered = total_len()
                strip = covered * (y - prev_y)

                if target >= 0 and covered > 0 and acc + strip >= target:
                    return prev_y + (target - acc) / covered

                acc += strip

                while i < m and events[i][0] == y:
                    _, x1, x2, typ = events[i]
                    i += 1
                    l, r = xi[x1], xi[x2] - 1
                    if n > 0 and l <= r:
                        update(1, 0, n - 1, l, r, typ)

                prev_y = y

            return acc if target < 0 else float(prev_y)

        total = sweep(-1.0)

        # reset tree for second sweep
        for i in range(len(cover)):
            cover[i] = 0
            length[i] = 0.0

        return sweep(total / 2.0)
var separateSquares = function(squares) {
  const events = [];
  const xVals = [];

  for (const [x, y, l] of squares) {
    const x2 = x + l, y2 = y + l;
    events.push([y, x, x2, 1]);
    events.push([y2, x, x2, -1]);
    xVals.push(x, x2);
  }

  xVals.sort((a, b) => a - b);
  const xs = [];
  for (const x of xVals) {
    if (xs.length === 0 || xs[xs.length - 1] !== x) xs.push(x);
  }

  const xi = new Map();
  xs.forEach((x, i) => xi.set(x, i));
  events.sort((a, b) => a[0] - b[0]);

  const n = xs.length - 1;
  const cover = new Array(4 * Math.max(1, n) + 5).fill(0);
  const len = new Array(4 * Math.max(1, n) + 5).fill(0);

  const pushUp = (idx, l, r) => {
    if (cover[idx] > 0) {
      len[idx] = xs[r + 1] - xs[l];
    } else if (l === r) {
      len[idx] = 0;
    } else {
      len[idx] = len[idx * 2] + len[idx * 2 + 1];
    }
  };

  const update = (idx, l, r, ql, qr, delta) => {
    if (ql > r || qr < l) return;
    if (ql <= l && r <= qr) {
      cover[idx] += delta;
      pushUp(idx, l, r);
      return;
    }
    const mid = (l + r) >> 1;
    update(idx * 2, l, mid, ql, qr, delta);
    update(idx * 2 + 1, mid + 1, r, ql, qr, delta);
    pushUp(idx, l, r);
  };

  const totalLen = () => (n <= 0 ? 0 : len[1]);

  const sweep = (target) => {
    let i = 0;
    let prevY = events[0][0];
    let acc = 0;

    while (i < events.length) {
      const y = events[i][0];
      const covered = totalLen();
      const strip = covered * (y - prevY);

      if (target >= 0 && covered > 0 && acc + strip >= target) {
        return prevY + (target - acc) / covered;
      }

      acc += strip;

      while (i < events.length && events[i][0] === y) {
        const [, x1, x2, typ] = events[i++];
        const l = xi.get(x1), r = xi.get(x2) - 1;
        if (n > 0 && l <= r) update(1, 0, n - 1, l, r, typ);
      }

      prevY = y;
    }

    return target < 0 ? acc : prevY;
  };

  const total = sweep(-1);
  cover.fill(0);
  len.fill(0);
  return sweep(total / 2);
};

中文

题目概述

给定多个轴对齐正方形 [x, y, l],面积按并集计算(重叠只算一次)。要求找到最小水平线 Y,使得线下并集面积等于线上并集面积。

核心思路

这题和 I 最大区别是“重叠不能重复计数”,所以不能逐个正方形累加,要做扫描线:

1)每个正方形拆成两个 y 事件:y 进入、y+l 离开,对应 x 区间 [x, x+l)
2)相邻两条事件 y 之间,当前被覆盖的 x 总长度不变。
3)这一条横向条带面积 = coveredX * deltaY

用“x 坐标压缩 + 线段树维护覆盖长度”即可在 O(log n) 更新活跃区间。

算法步骤

1)构建事件并做 x 坐标离散化。
2)第一遍扫描得到并集总面积 total
3)第二遍扫描累计面积,首次达到 total/2 时答案就在当前条带。
4)条带内覆盖长度恒定,直接线性插值:
ansY = y0 + (target - prefixArea) / coveredX

复杂度分析

设正方形数量为 n,事件数 2n
时间复杂度:O(n log n)
空间复杂度:O(n)

多语言参考实现(Java / Go / C++ / Python / JavaScript)

import java.util.*;

class Solution {
    static class Event {
        long y, x1, x2;
        int type;
        Event(long y, long x1, long x2, int type) {
            this.y = y; this.x1 = x1; this.x2 = x2; this.type = type;
        }
    }

    static class SegTree {
        int n;
        int[] cover;
        double[] len;
        long[] xs;

        SegTree(long[] xs) {
            this.xs = xs;
            this.n = xs.length - 1;
            this.cover = new int[n * 4 + 5];
            this.len = new double[n * 4 + 5];
        }

        void update(int idx, int l, int r, int ql, int qr, int delta) {
            if (ql > r || qr < l) return;
            if (ql <= l && r <= qr) {
                cover[idx] += delta;
                pushUp(idx, l, r);
                return;
            }
            int mid = (l + r) >>> 1;
            update(idx << 1, l, mid, ql, qr, delta);
            update(idx << 1 | 1, mid + 1, r, ql, qr, delta);
            pushUp(idx, l, r);
        }

        void pushUp(int idx, int l, int r) {
            if (cover[idx] > 0) {
                len[idx] = xs[r + 1] - xs[l];
            } else if (l == r) {
                len[idx] = 0;
            } else {
                len[idx] = len[idx << 1] + len[idx << 1 | 1];
            }
        }

        double totalLen() { return n <= 0 ? 0.0 : len[1]; }
    }

    public double separateSquares(int[][] squares) {
        List<Event> events = new ArrayList<>();
        List<Long> xVals = new ArrayList<>();

        for (int[] s : squares) {
            long x = s[0], y = s[1], l = s[2];
            long x2 = x + l, y2 = y + l;
            events.add(new Event(y, x, x2, +1));
            events.add(new Event(y2, x, x2, -1));
            xVals.add(x);
            xVals.add(x2);
        }

        Collections.sort(xVals);
        long[] xs = xVals.stream().distinct().mapToLong(Long::longValue).toArray();
        Map<Long, Integer> xi = new HashMap<>();
        for (int i = 0; i < xs.length; i++) xi.put(xs[i], i);

        events.sort(Comparator.comparingLong(e -> e.y));

        double total = sweepArea(events, xs, xi, -1.0);
        double target = total / 2.0;
        return sweepArea(events, xs, xi, target);
    }

    private double sweepArea(List<Event> events, long[] xs, Map<Long, Integer> xi, double target) {
        SegTree st = new SegTree(xs);
        int m = events.size();
        int i = 0;
        long prevY = events.get(0).y;
        double acc = 0.0;

        while (i < m) {
            long y = events.get(i).y;
            double covered = st.totalLen();
            double strip = covered * (y - prevY);

            if (target >= 0 && covered > 0 && acc + strip >= target) {
                return prevY + (target - acc) / covered;
            }

            acc += strip;

            while (i < m && events.get(i).y == y) {
                Event e = events.get(i++);
                int l = xi.get(e.x1), r = xi.get(e.x2) - 1;
                if (l <= r) st.update(1, 0, st.n - 1, l, r, e.type);
            }
            prevY = y;
        }

        return target < 0 ? acc : prevY;
    }
}
import "sort"

type Event struct {
    y, x1, x2 int64
    typ       int
}

type SegTree struct {
    n     int
    cover []int
    length []float64
    xs    []int64
}

func NewSegTree(xs []int64) *SegTree {
    n := len(xs) - 1
    return &SegTree{n: n, cover: make([]int, 4*n+5), length: make([]float64, 4*n+5), xs: xs}
}

func (st *SegTree) pushUp(idx, l, r int) {
    if st.cover[idx] > 0 {
        st.length[idx] = float64(st.xs[r+1] - st.xs[l])
    } else if l == r {
        st.length[idx] = 0
    } else {
        st.length[idx] = st.length[idx*2] + st.length[idx*2+1]
    }
}

func (st *SegTree) update(idx, l, r, ql, qr, delta int) {
    if ql > r || qr < l {
        return
    }
    if ql <= l && r <= qr {
        st.cover[idx] += delta
        st.pushUp(idx, l, r)
        return
    }
    mid := (l + r) / 2
    st.update(idx*2, l, mid, ql, qr, delta)
    st.update(idx*2+1, mid+1, r, ql, qr, delta)
    st.pushUp(idx, l, r)
}

func (st *SegTree) totalLen() float64 {
    if st.n <= 0 { return 0 }
    return st.length[1]
}

func separateSquares(squares [][]int) float64 {
    events := make([]Event, 0, len(squares)*2)
    xVals := make([]int64, 0, len(squares)*2)

    for _, s := range squares {
        x, y, l := int64(s[0]), int64(s[1]), int64(s[2])
        x2, y2 := x+l, y+l
        events = append(events, Event{y, x, x2, 1}, Event{y2, x, x2, -1})
        xVals = append(xVals, x, x2)
    }

    sort.Slice(xVals, func(i, j int) bool { return xVals[i] < xVals[j] })
    xs := make([]int64, 0, len(xVals))
    for _, x := range xVals {
        if len(xs) == 0 || xs[len(xs)-1] != x { xs = append(xs, x) }
    }

    xi := map[int64]int{}
    for i, x := range xs { xi[x] = i }

    sort.Slice(events, func(i, j int) bool { return events[i].y < events[j].y })

    total := sweep(events, xs, xi, -1)
    return sweep(events, xs, xi, total/2.0)
}

func sweep(events []Event, xs []int64, xi map[int64]int, target float64) float64 {
    st := NewSegTree(xs)
    i, m := 0, len(events)
    prevY := events[0].y
    acc := 0.0

    for i < m {
        y := events[i].y
        covered := st.totalLen()
        strip := covered * float64(y-prevY)

        if target >= 0 && covered > 0 && acc+strip >= target {
            return float64(prevY) + (target-acc)/covered
        }
        acc += strip

        for i < m && events[i].y == y {
            e := events[i]
            i++
            l, r := xi[e.x1], xi[e.x2]-1
            if l <= r {
                st.update(1, 0, st.n-1, l, r, e.typ)
            }
        }
        prevY = y
    }

    if target < 0 { return acc }
    return float64(prevY)
}
class Solution {
    struct Event {
        long long y, x1, x2;
        int type;
        bool operator<(const Event& other) const { return y < other.y; }
    };

    struct SegTree {
        int n;
        vector<int> cover;
        vector<double> len;
        vector<long long> xs;

        SegTree(const vector<long long>& x): xs(x) {
            n = (int)xs.size() - 1;
            cover.assign(4 * n + 5, 0);
            len.assign(4 * n + 5, 0.0);
        }

        void pushUp(int idx, int l, int r) {
            if (cover[idx] > 0) len[idx] = (double)(xs[r + 1] - xs[l]);
            else if (l == r) len[idx] = 0.0;
            else len[idx] = len[idx * 2] + len[idx * 2 + 1];
        }

        void update(int idx, int l, int r, int ql, int qr, int delta) {
            if (ql > r || qr < l) return;
            if (ql <= l && r <= qr) {
                cover[idx] += delta;
                pushUp(idx, l, r);
                return;
            }
            int mid = (l + r) / 2;
            update(idx * 2, l, mid, ql, qr, delta);
            update(idx * 2 + 1, mid + 1, r, ql, qr, delta);
            pushUp(idx, l, r);
        }

        double totalLen() const { return n <= 0 ? 0.0 : len[1]; }
    };

    double sweep(const vector<Event>& events, const vector<long long>& xs,
                 unordered_map<long long, int>& xi, double target) {
        SegTree st(xs);
        int i = 0, m = events.size();
        long long prevY = events[0].y;
        double acc = 0.0;

        while (i < m) {
            long long y = events[i].y;
            double covered = st.totalLen();
            double strip = covered * (double)(y - prevY);

            if (target >= 0 && covered > 0 && acc + strip >= target) {
                return (double)prevY + (target - acc) / covered;
            }
            acc += strip;

            while (i < m && events[i].y == y) {
                auto& e = events[i++];
                int l = xi[e.x1], r = xi[e.x2] - 1;
                if (l <= r) st.update(1, 0, st.n - 1, l, r, e.type);
            }
            prevY = y;
        }

        return target < 0 ? acc : (double)prevY;
    }

public:
    double separateSquares(vector<vector<int>>& squares) {
        vector<Event> events;
        vector<long long> xVals;

        for (auto& s : squares) {
            long long x = s[0], y = s[1], l = s[2];
            long long x2 = x + l, y2 = y + l;
            events.push_back({y, x, x2, +1});
            events.push_back({y2, x, x2, -1});
            xVals.push_back(x);
            xVals.push_back(x2);
        }

        sort(xVals.begin(), xVals.end());
        xVals.erase(unique(xVals.begin(), xVals.end()), xVals.end());

        unordered_map<long long, int> xi;
        for (int i = 0; i < (int)xVals.size(); ++i) xi[xVals[i]] = i;

        sort(events.begin(), events.end());

        double total = sweep(events, xVals, xi, -1.0);
        return sweep(events, xVals, xi, total / 2.0);
    }
};
from typing import List

class Solution:
    def separateSquares(self, squares: List[List[int]]) -> float:
        events = []
        xs = []

        for x, y, l in squares:
            x2, y2 = x + l, y + l
            events.append((y, x, x2, 1))
            events.append((y2, x, x2, -1))
            xs.extend([x, x2])

        xs = sorted(set(xs))
        xi = {x: i for i, x in enumerate(xs)}
        events.sort()

        n = len(xs) - 1
        cover = [0] * (4 * max(1, n) + 5)
        length = [0.0] * (4 * max(1, n) + 5)

        def push_up(idx: int, l: int, r: int) -> None:
            if cover[idx] > 0:
                length[idx] = xs[r + 1] - xs[l]
            elif l == r:
                length[idx] = 0.0
            else:
                length[idx] = length[idx * 2] + length[idx * 2 + 1]

        def update(idx: int, l: int, r: int, ql: int, qr: int, delta: int) -> None:
            if ql > r or qr < l:
                return
            if ql <= l and r <= qr:
                cover[idx] += delta
                push_up(idx, l, r)
                return
            mid = (l + r) // 2
            update(idx * 2, l, mid, ql, qr, delta)
            update(idx * 2 + 1, mid + 1, r, ql, qr, delta)
            push_up(idx, l, r)

        def total_len() -> float:
            return 0.0 if n <= 0 else length[1]

        def sweep(target: float) -> float:
            i = 0
            m = len(events)
            prev_y = events[0][0]
            acc = 0.0

            while i < m:
                y = events[i][0]
                covered = total_len()
                strip = covered * (y - prev_y)

                if target >= 0 and covered > 0 and acc + strip >= target:
                    return prev_y + (target - acc) / covered

                acc += strip

                while i < m and events[i][0] == y:
                    _, x1, x2, typ = events[i]
                    i += 1
                    l, r = xi[x1], xi[x2] - 1
                    if n > 0 and l <= r:
                        update(1, 0, n - 1, l, r, typ)

                prev_y = y

            return acc if target < 0 else float(prev_y)

        total = sweep(-1.0)

        # reset tree for second sweep
        for i in range(len(cover)):
            cover[i] = 0
            length[i] = 0.0

        return sweep(total / 2.0)
var separateSquares = function(squares) {
  const events = [];
  const xVals = [];

  for (const [x, y, l] of squares) {
    const x2 = x + l, y2 = y + l;
    events.push([y, x, x2, 1]);
    events.push([y2, x, x2, -1]);
    xVals.push(x, x2);
  }

  xVals.sort((a, b) => a - b);
  const xs = [];
  for (const x of xVals) {
    if (xs.length === 0 || xs[xs.length - 1] !== x) xs.push(x);
  }

  const xi = new Map();
  xs.forEach((x, i) => xi.set(x, i));
  events.sort((a, b) => a[0] - b[0]);

  const n = xs.length - 1;
  const cover = new Array(4 * Math.max(1, n) + 5).fill(0);
  const len = new Array(4 * Math.max(1, n) + 5).fill(0);

  const pushUp = (idx, l, r) => {
    if (cover[idx] > 0) {
      len[idx] = xs[r + 1] - xs[l];
    } else if (l === r) {
      len[idx] = 0;
    } else {
      len[idx] = len[idx * 2] + len[idx * 2 + 1];
    }
  };

  const update = (idx, l, r, ql, qr, delta) => {
    if (ql > r || qr < l) return;
    if (ql <= l && r <= qr) {
      cover[idx] += delta;
      pushUp(idx, l, r);
      return;
    }
    const mid = (l + r) >> 1;
    update(idx * 2, l, mid, ql, qr, delta);
    update(idx * 2 + 1, mid + 1, r, ql, qr, delta);
    pushUp(idx, l, r);
  };

  const totalLen = () => (n <= 0 ? 0 : len[1]);

  const sweep = (target) => {
    let i = 0;
    let prevY = events[0][0];
    let acc = 0;

    while (i < events.length) {
      const y = events[i][0];
      const covered = totalLen();
      const strip = covered * (y - prevY);

      if (target >= 0 && covered > 0 && acc + strip >= target) {
        return prevY + (target - acc) / covered;
      }

      acc += strip;

      while (i < events.length && events[i][0] === y) {
        const [, x1, x2, typ] = events[i++];
        const l = xi.get(x1), r = xi.get(x2) - 1;
        if (n > 0 && l <= r) update(1, 0, n - 1, l, r, typ);
      }

      prevY = y;
    }

    return target < 0 ? acc : prevY;
  };

  const total = sweep(-1);
  cover.fill(0);
  len.fill(0);
  return sweep(total / 2);
};

Comments