{x}
blog image

Falling Squares

There are several squares being dropped onto the X-axis of a 2D plane.

You are given a 2D integer array positions where positions[i] = [lefti, sideLengthi] represents the ith square with a side length of sideLengthi that is dropped with its left edge aligned with X-coordinate lefti.

Each square is dropped one at a time from a height above any landed squares. It then falls downward (negative Y direction) until it either lands on the top side of another square or on the X-axis. A square brushing the left/right side of another square does not count as landing on it. Once it lands, it freezes in place and cannot be moved.

After each square is dropped, you must record the height of the current tallest stack of squares.

Return an integer array ans where ans[i] represents the height described above after dropping the ith square.

 

Example 1:

Input: positions = [[1,2],[2,3],[6,1]]
Output: [2,5,5]
Explanation:
After the first drop, the tallest stack is square 1 with a height of 2.
After the second drop, the tallest stack is squares 1 and 2 with a height of 5.
After the third drop, the tallest stack is still squares 1 and 2 with a height of 5.
Thus, we return an answer of [2, 5, 5].

Example 2:

Input: positions = [[100,100],[200,100]]
Output: [100,100]
Explanation:
After the first drop, the tallest stack is square 1 with a height of 100.
After the second drop, the tallest stack is either square 1 or square 2, both with heights of 100.
Thus, we return an answer of [100, 100].
Note that square 2 only brushes the right side of square 1, which does not count as landing on it.

 

Constraints:

  • 1 <= positions.length <= 1000
  • 1 <= lefti <= 108
  • 1 <= sideLengthi <= 106

699. Falling Squares

Problem Description

The problem involves simulating the dropping of squares onto the x-axis. Each square has a left edge position and a side length. Squares fall until they land on the x-axis or another square. The goal is to return an array where each element represents the maximum height of any stack of squares after each square is dropped.

Approach: Segment Tree

The most efficient approach is to use a segment tree. A segment tree is a data structure that efficiently handles range queries and updates. In this case, we'll use it to track the maximum height at each point along the x-axis.

Why Segment Tree?

  • Range Queries: We need to quickly find the maximum height within the range where a new square will land. A segment tree allows us to perform this query in O(log n) time, where n is the number of squares.
  • Updates: When a square lands, it changes the maximum height in a range of the x-axis. A segment tree can efficiently update this range in O(log n) time.

Segment Tree Implementation Details:

  1. Node Structure: Each node in the segment tree represents a range of x-coordinates. It stores:

    • l, r: The start and end x-coordinates of the range.
    • v: The maximum height in this range.
    • add: A lazy propagation value (explained below). This helps efficiently propagate updates down the tree.
  2. Building the Tree: We start with a root node covering the entire x-axis. Nodes are recursively created to subdivide the range.

  3. query(l, r): This function finds the maximum height within the range [l, r]. It recursively traverses the tree, using pushdown to handle lazy updates.

  4. modify(l, r, v): This function updates the maximum height in the range [l, r] to v. It uses pushdown and pushup to maintain the tree's consistency.

  5. pushdown(node): This propagates the add value down to the children of a node.

  6. pushup(node): This updates the v value of a node based on the v values of its children.

  7. Dynamic Node Creation: Because the x-coordinates can be large, we create nodes only when needed, making the space complexity more efficient.

Code Implementation (Python)

class Node:
    def __init__(self, l, r):
        self.left = None
        self.right = None
        self.l = l
        self.r = r
        self.mid = (l + r) >> 1
        self.v = 0
        self.add = 0
 
class SegmentTree:
    def __init__(self):
        self.root = Node(1, int(1e9))  # Adjust range as needed
 
    # ... (query, modify, pushup, pushdown methods as described above) ...
 
class Solution:
    def fallingSquares(self, positions: List[List[int]]) -> List[int]:
        ans = []
        mx = 0
        tree = SegmentTree()
        for l, w in positions:
            r = l + w - 1
            h = tree.query(l, r) + w
            mx = max(mx, h)
            ans.append(mx)
            tree.modify(l, r, h)
        return ans

(Note: The complete query, modify, pushup, and pushdown methods for the SegmentTree class would be added here. Refer to the detailed solution in the original response for the complete code in Python, Java, C++, Go and TypeScript.)

Time and Space Complexity

  • Time Complexity: O(n log n), where n is the number of squares. Each square's insertion involves a query and a modification operation in the segment tree, both of which take O(log n) time.

  • Space Complexity: O(n) in the worst case (although it can be less due to dynamic node creation). The space is mainly used to store the segment tree nodes. The size of the tree is related to the number of squares and the range of x-coordinates.

Other Approaches

While other approaches like brute force are possible, they would be significantly less efficient (likely O(n^2) or worse) due to the need to repeatedly check for overlaps and calculate maximum heights. The segment tree provides the optimal time complexity for this problem.