DEV Community

Cover image for 🎭Beginner-Friendly Guide 'Separate Squares II' – LeetCode 3454 (C++, Python, JavaScript)
Om Shree
Om Shree

Posted on

🎭Beginner-Friendly Guide 'Separate Squares II' – LeetCode 3454 (C++, Python, JavaScript)

Finding the perfect horizontal line that splits a set of overlapping shapes into two equal areas is a classic geometry problem. When shapes overlap, we cannot simply sum their individual areas: we must find the area of their union, which adds a significant layer of complexity.


Problem Summary

You're given:

  • An array of squares where each square is defined by its bottom-left corner and its side length .
  • A scenario where squares may overlap, and overlapping regions are only counted once.

Your goal:

  • Find the minimum -coordinate of a horizontal line such that the total area of the squares below the line is equal to the total area of the squares above the line.

Example 1: Input: squares = [[0,0,1],[2,2,1]]

Output: 1.00000

Explanation:

Image description : Ex1

Any horizontal line between y = 1 and y = 2 results in an equal split, with 1 square unit above and 1 square unit below. The minimum y-value is 1.

Example 2: Input: squares = [[0,0,2],[1,1,1]]

Output: 1.00000

Explanation:

Image description: "

Since the blue square overlaps with the red square, it will not be counted again. Thus, the line y = 1 splits the squares into two equal parts.


Intuition

To solve this, we use a Sweep Line Algorithm combined with a Segment Tree. Imagine a horizontal line moving from the bottom-most -coordinate to the top-most. As this line moves, the "width" of the squares it intersects changes.

  1. Coordinate Compression: The -coordinates can be as large as , but there are at most unique -coordinates (start and end points of squares). we map these large values to a smaller range of indices to use them in a Segment Tree.
  2. The Sweep Line: we create events for the bottom and top of every square. As we sweep from bottom to top, we maintain the "active" horizontal width (the union of -intervals) using the Segment Tree.
  3. Area Calculation: The area between two -events is simply . By summing these, we get the total area.
  4. Finding the Median: Once we have the total area, we traverse the -stages again. When we reach the stage where the cumulative area hits , we use linear interpolation to find the exact -coordinate.

Code Blocks

C++ Solution

#include <vector>
#include <algorithm>
#include <numeric>

using namespace std;

class Solution {
    struct SegTree {
        vector<int> count;
        vector<long long> total_len;
        const vector<int>& coords;
        int n;

        SegTree(const vector<int>& x_coords) : coords(x_coords) {
            n = x_coords.size();
            count.assign(4 * n, 0);
            total_len.assign(4 * n, 0);
        }

        void update(int node, int start, int end, int L, int R, int val) {
            if (L <= start && end <= R) {
                count[node] += val;
            } else {
                int mid = (start + end) / 2;
                if (L <= mid) update(2 * node, start, mid, L, R, val);
                if (R > mid) update(2 * node + 1, mid + 1, end, L, R, val);
            }
            if (count[node] > 0) {
                total_len[node] = coords[end + 1] - coords[start];
            } else if (start != end) {
                total_len[node] = total_len[2 * node] + total_len[2 * node + 1];
            } else {
                total_len[node] = 0;
            }
        }
    };

public:
    double separateSquares(vector<vector<int>>& squares) {
        vector<int> x_coords;
        vector<vector<int>> events;
        for (auto& s : squares) {
            x_coords.push_back(s[0]);
            x_coords.push_back(s[0] + s[2]);
            events.push_back({s[1], 1, s[0], s[0] + s[2]});
            events.push_back({s[1] + s[2], -1, s[0], s[0] + s[2]});
        }
        sort(x_coords.begin(), x_coords.end());
        x_coords.erase(unique(x_coords.begin(), x_coords.end()), x_coords.end());
        sort(events.begin(), events.end());

        SegTree st(x_coords);
        vector<pair<double, long long>> stages;
        double total_area = 0;

        for (int i = 0; i < events.size() - 1; ++i) {
            int L = lower_bound(x_coords.begin(), x_coords.end(), events[i][2]) - x_coords.begin();
            int R = lower_bound(x_coords.begin(), x_coords.end(), events[i][3]) - x_coords.begin() - 1;
            st.update(1, 0, x_coords.size() - 2, L, R, events[i][1]);

            double height = events[i+1][0] - events[i][0];
            long long width = st.total_len[1];
            total_area += height * width;
            stages.push_back({(double)events[i][0], width});
        }

        double half_area = total_area / 2.0;
        double current_area = 0;
        for (int i = 0; i < stages.size(); ++i) {
            double next_y = (i + 1 < events.size() - 1) ? events[i+1][0] : events.back()[0];
            double stage_area = (next_y - stages[i].first) * stages[i].second;
            if (current_area + stage_area >= half_area - 1e-9) {
                return stages[i].first + (half_area - current_area) / stages[i].second;
            }
            current_area += stage_area;
        }
        return events.back()[0];
    }
};

Enter fullscreen mode Exit fullscreen mode

Python Solution

from bisect import bisect_left

class SegTree:
    def __init__(self, x_coords):
        self.coords = x_coords
        self.n = len(x_coords)
        self.count = [0] * (4 * self.n)
        self.total_len = [0] * (4 * self.n)

    def update(self, node, start, end, L, R, val):
        if L <= start and end <= R:
            self.count[node] += val
        else:
            mid = (start + end) // 2
            if L <= mid:
                self.update(2 * node, start, mid, L, R, val)
            if R > mid:
                self.update(2 * node + 1, mid + 1, end, L, R, val)

        if self.count[node] > 0:
            self.total_len[node] = self.coords[end + 1] - self.coords[start]
        elif start != end:
            self.total_len[node] = self.total_len[2 * node] + self.total_len[2 * node + 1]
        else:
            self.total_len[node] = 0

class Solution:
    def separateSquares(self, squares: list[list[int]]) -> float:
        x_coords = sorted(list(set([s[0] for s in squares] + [s[0] + s[2] for s in squares])))
        events = []
        for x, y, l in squares:
            events.append((y, 1, x, x + l))
            events.append((y + l, -1, x, x + l))
        events.sort()

        st = SegTree(x_coords)
        stages = []
        total_area = 0

        for i in range(len(events) - 1):
            L = bisect_left(x_coords, events[i][2])
            R = bisect_left(x_coords, events[i][3]) - 1
            st.update(1, 0, len(x_coords) - 2, L, R, events[i][1])

            height = events[i+1][0] - events[i][0]
            width = st.total_len[1]
            total_area += height * width
            stages.append((events[i][0], width, events[i+1][0]))

        half_area = total_area / 2.0
        current_area = 0
        for y_start, width, y_end in stages:
            stage_area = (y_end - y_start) * width
            if current_area + stage_area >= half_area - 1e-9:
                if width == 0: return y_start
                return y_start + (half_area - current_area) / width
            current_area += stage_area
        return events[-1][0]

Enter fullscreen mode Exit fullscreen mode

JavaScript Solution

class SegTree {
    constructor(xCoords) {
        this.coords = xCoords;
        this.n = xCoords.length;
        this.count = new Int32Array(4 * this.n);
        this.totalLen = new Float64Array(4 * this.n);
    }

    update(node, start, end, L, R, val) {
        if (L <= start && end <= R) {
            this.count[node] += val;
        } else {
            const mid = Math.floor((start + end) / 2);
            if (L <= mid) this.update(2 * node, start, mid, L, R, val);
            if (R > mid) this.update(2 * node + 1, mid + 1, end, L, R, val);
        }

        if (this.count[node] > 0) {
            this.totalLen[node] = this.coords[end + 1] - this.coords[start];
        } else if (start !== end) {
            this.totalLen[node] = this.totalLen[2 * node] + this.totalLen[2 * node + 1];
        } else {
            this.totalLen[node] = 0;
        }
    }
}

var separateSquares = function(squares) {
    let xSet = new Set();
    for (const [x, y, l] of squares) {
        xSet.add(x);
        xSet.add(x + l);
    }
    const xCoords = Array.from(xSet).sort((a, b) => a - b);
    const events = [];
    for (const [x, y, l] of squares) {
        events.push([y, 1, x, x + l]);
        events.push([y + l, -1, x, x + l]);
    }
    events.sort((a, b) => a[0] - b[0]);

    const st = new SegTree(xCoords);
    const stages = [];
    let totalArea = 0;

    const findIdx = (val) => {
        let low = 0, high = xCoords.length - 1;
        while (low <= high) {
            let mid = Math.floor((low + high) / 2);
            if (xCoords[mid] < val) low = mid + 1;
            else high = mid - 1;
        }
        return low;
    };

    for (let i = 0; i < events.length - 1; i++) {
        const L = findIdx(events[i][2]);
        const R = findIdx(events[i][3]) - 1;
        st.update(1, 0, xCoords.length - 2, L, R, events[i][1]);

        const height = events[i+1][0] - events[i][0];
        const width = st.totalLen[1];
        totalArea += height * width;
        stages.push({ yStart: events[i][0], width, yEnd: events[i+1][0] });
    }

    const halfArea = totalArea / 2;
    let currentArea = 0;
    for (const stage of stages) {
        const stageArea = (stage.yEnd - stage.yStart) * stage.width;
        if (currentArea + stageArea >= halfArea - 1e-9) {
            return stage.yStart + (halfArea - currentArea) / stage.width;
        }
        currentArea += stageArea;
    }
    return events[events.length - 1][0];
};

Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • Coordinate Compression: A vital technique when working with sparse but large ranges. It maps space into a manageable index space of size .
  • Sweep Line Algorithm: This turns a 2D area problem into a 1D "length over time" problem. It is the standard approach for rectangle union problems.
  • Segment Tree for Union: Unlike a standard Segment Tree that might store "sum" or "max", this version tracks the total covered length of intervals, even when those intervals overlap.

Final Thoughts

This problem is a fantastic example of how specialized data structures like Segment Trees are used in computational geometry. In the real world, these algorithms are foundational for VLSI Design (arranging components on a chip), Computer Graphics (rendering windows or sprites), and Geographic Information Systems (GIS). Mastering the sweep line technique will make you a much stronger candidate for high-level engineering roles that involve spatial reasoning.

Top comments (0)