Simulate falling squares on a number line and track the maximum stack height after each placement.
Problem Restatement
We are given a list of squares falling onto the X-axis.
Each square is described by:
[left, side_length]The square spans the interval:
[left, left + side_length)The square falls vertically until:
| Situation | Result |
|---|---|
| It hits the ground | Stops at height 0 |
| It lands on another square | Stacks on top |
After each square falls, we must report the maximum height among all squares so far.
The official statement models squares falling onto a 1D number line and asks for the highest stack after each placement. (leetcode.com)
Input and Output
| Item | Meaning |
|---|---|
| Input | List positions of [left, side_length] |
| Output | List of maximum heights after each square |
| Square interval | [left, left + side_length) |
| Falling direction | Vertical |
| Collision rule | Overlapping intervals stack |
Example function shape:
def fallingSquares(positions: list[list[int]]) -> list[int]:
...Examples
Example 1:
positions = [[1, 2], [2, 3], [6, 1]]First square
Square:
[1, 3)Side length:
2It lands on the ground.
Height becomes:
2Maximum height:
2Second square
Square:
[2, 5)It overlaps the first square on interval:
[2, 3)So it lands on top of height 2.
New height:
2 + 3 = 5Maximum height:
5Third square
Square:
[6, 7)No overlap with previous squares.
It lands on the ground with height:
1The global maximum remains:
5Answer:
[2, 5, 5]First Thought: Simulate the Plane
A direct idea is to model the entire coordinate line and fill heights.
But coordinates can be very large:
0 <= left <= 10^8So we cannot build a huge array.
Instead, we only care about intervals created by squares.
Key Insight
When a new square falls:
[left, right)its base height equals the maximum height among all previous overlapping intervals.
Suppose the current maximum overlapping height is:
baseThen the new square’s top height is:
base + side_lengthSo for each new square, we need:
- Find the maximum height among overlapping previous squares.
- Add the side length.
- Store the new interval and its height.
Overlap Rule
Two intervals overlap if:
max(left1, left2) < min(right1, right2)For example:
| Interval A | Interval B | Overlap? |
|---|---|---|
[1,3) | [2,5) | Yes |
[1,2) | [2,4) | No |
[5,7) | [1,5) | No |
The intervals are half-open.
Touching edges do not overlap.
Simple Interval Simulation
We store placed squares as:
(left, right, height)For each new square:
- Compute:
right = left + size - Initialize:
base_height = 0 - Check every previous interval:
- if overlapping:
base_height = max(base_height, previous_height)
- if overlapping:
- New height:
new_height = base_height + size - Save the interval.
- Track the global maximum.
Walkthrough
Consider:
positions = [[1, 2], [2, 3], [6, 1]]Square 1
Interval:
[1,3)No previous intervals.
Height:
2Global maximum:
2Square 2
Interval:
[2,5)Overlaps:
[1,3)Base height:
2New height:
2 + 3 = 5Global maximum:
5Square 3
Interval:
[6,7)No overlap.
Height:
1Global maximum stays:
5Answer:
[2, 5, 5]Correctness
Suppose a new square spans interval:
[left, right)The square falls vertically until it reaches either:
| Situation | Landing height |
|---|---|
| Ground | 0 |
| Top of an overlapping square | That square’s height |
A square can only land on squares whose horizontal intervals overlap its interval.
Therefore, the correct landing base is the maximum height among all previously placed overlapping intervals.
The algorithm checks every previous interval and computes exactly this maximum.
The new square then occupies height:
base_height + side_lengthwhich matches the physical stacking rule.
The algorithm stores this new interval and height for future squares.
After each placement, the global maximum height among all squares is updated and appended to the answer list.
Thus, every reported height is correct.
Complexity
Let n = len(positions).
| Metric | Value | Why |
|---|---|---|
| Time | O(n^2) | Each square may compare against all previous squares |
| Space | O(n) | Store intervals and heights |
This simple solution passes because the official constraints are relatively small.
More advanced solutions use:
| Technique | Complexity |
|---|---|
| Coordinate compression + segment tree | O(n log n) |
| Balanced interval structures | O(n log n) |
Implementation
class Solution:
def fallingSquares(self, positions: list[list[int]]) -> list[int]:
intervals = []
answer = []
current_max = 0
for left, size in positions:
right = left + size
base_height = 0
for prev_left, prev_right, prev_height in intervals:
overlap = max(left, prev_left) < min(right, prev_right)
if overlap:
base_height = max(base_height, prev_height)
new_height = base_height + size
intervals.append((left, right, new_height))
current_max = max(current_max, new_height)
answer.append(current_max)
return answerCode Explanation
We store all placed squares as:
(left, right, height)For each new square:
for left, size in positions:we compute its interval:
right = left + sizeInitially, the square could land on the ground:
base_height = 0We compare against every previous square:
for prev_left, prev_right, prev_height in intervals:The overlap condition is:
max(left, prev_left) < min(right, prev_right)If overlapping, the new square may stack higher:
base_height = max(base_height, prev_height)The new square’s top height is:
new_height = base_height + sizeThen we save the interval:
intervals.append((left, right, new_height))Finally, update the global maximum:
current_max = max(current_max, new_height)and append it to the answer list.
Coordinate Compression Idea
The optimized solution compresses all unique coordinates:
left
left + sizeinto small indices.
Then a segment tree or lazy propagation structure supports:
| Operation | Meaning |
|---|---|
| Range maximum query | Find base height |
| Range update | Set new height |
This improves the complexity to roughly:
O(n log n)but the interval simulation solution is simpler and sufficient for many interview settings.
Testing
def run_tests():
s = Solution()
assert s.fallingSquares(
[[1, 2], [2, 3], [6, 1]]
) == [2, 5, 5]
assert s.fallingSquares(
[[100, 100], [200, 100]]
) == [100, 100]
assert s.fallingSquares(
[[1, 5], [2, 2], [7, 1]]
) == [5, 7, 7]
assert s.fallingSquares(
[[1, 2], [1, 2], [1, 2]]
) == [2, 4, 6]
assert s.fallingSquares(
[[1, 2], [3, 2], [5, 2]]
) == [2, 2, 2]
print("all tests passed")
run_tests()Test meaning:
| Test | Expected | Why |
|---|---|---|
[[1,2],[2,3],[6,1]] | [2,5,5] | Official example |
| Separate large intervals | [100,100] | No overlap |
| Nested overlap | [5,7,7] | Second square stacks on first |
| Same interval repeated | [2,4,6] | Heights accumulate |
| Touching but not overlapping intervals | [2,2,2] | Half-open intervals do not overlap |