LeetCode 218: The Skyline Problem
the skyline problem is one of the most challenging problems i've encountered on leetcode. it's a perfect example of how geometric problems can be solved using algorithmic techniques like line sweep and priority queues.
Problem Statement
A city's skyline is the outer contour of the silhouette formed by all the buildings in that city when viewed from a distance. Given the locations and heights of all the buildings, return the skyline formed by these buildings collectively.
Input: buildings = [[2,9,10],[3,7,15],[5,12,12],[15,20,10],[19,24,8]]
Output: [[2,10],[3,15],[7,12],[12,0],[15,10],[20,8],[24,0]]
Understanding the Problem
The key insight is that the skyline is determined by the critical points where:
- A building starts (increases the height)
- A building ends (decreases the height)
- The maximum height changes
My Approach
i used a line sweep algorithm with a priority queue to track the current maximum height at each critical point.
Algorithm Overview
- **Extract critical points** - Convert each building into start and end events
- **Sort events** - Sort all events by x-coordinate
- **Process events** - Use a max heap to track current heights
- **Generate skyline** - Record points where the maximum height changes
My Solution
import heapq
from collections import defaultdict
def getSkyline(self, buildings: List[List[int]]) -> List[List[int]]:
# Step 1: Create events for each building
events = []
for left, right, height in buildings:
# Start event: (x, height, is_start)
events.append((left, height, True))
# End event: (x, height, is_start)
events.append((right, height, False))
# Step 2: Sort events by x-coordinate
events.sort()
# Step 3: Process events
result = []
max_heap = [0] # Initialize with ground level
active_heights = defaultdict(int) # Track active heights
for x, height, is_start in events:
if is_start:
# Building starts - add height to active set
active_heights[height] += 1
heapq.heappush(max_heap, -height) # Use negative for max heap
else:
# Building ends - remove height from active set
active_heights[height] -= 1
if active_heights[height] == 0:
del active_heights[height]
# Get current maximum height
while max_heap and -max_heap[0] not in active_heights:
heapq.heappop(max_heap)
current_max = -max_heap[0] if max_heap else 0
# Add to result if height changed
if not result or result[-1][1] != current_max:
result.append([x, current_max])
return resultCode Breakdown
Step 1: Event Creation
events = []
for left, right, height in buildings:
events.append((left, height, True)) # Start event
events.append((right, height, False)) # End eventWe convert each building into two events:
- **Start event**: When a building begins (increases height)
- **End event**: When a building ends (decreases height)
Step 2: Event Sorting
events.sort()Sort all events by x-coordinate to process them in order.
Step 3: Event Processing
max_heap = [0] # Track maximum heights
active_heights = defaultdict(int) # Count active buildings at each heightWe use:
- **Max heap**: To efficiently track the current maximum height
- **Active heights counter**: To handle overlapping buildings
Step 4: Height Management
if is_start:
active_heights[height] += 1
heapq.heappush(max_heap, -height)
else:
active_heights[height] -= 1
if active_heights[height] == 0:
del active_heights[height]For start events:
- Increment the count for this height
- Add height to max heap
For end events:
- Decrement the count for this height
- Remove height if no buildings are active at this height
Step 5: Cleanup and Result Generation
while max_heap and -max_heap[0] not in active_heights:
heapq.heappop(max_heap)
current_max = -max_heap[0] if max_heap else 0
if not result or result[-1][1] != current_max:
result.append([x, current_max])Cleanup: Remove heights from heap that are no longer active Result generation: Add point to skyline if height changed
Example Walkthrough
let's trace through the example: [[2,9,10],[3,7,15],[5,12,12],[15,20,10],[19,24,8]]
Events: [(2,10,True), (3,15,True), (5,12,True), (7,15,False), (9,10,False), (12,12,False), (15,10,True), (19,8,True), (20,10,False), (24,8,False)]
Processing:
1. x=2, height=10, start: active={10:1}, max=10, result=[[2,10]]
2. x=3, height=15, start: active={10:1,15:1}, max=15, result=[[2,10],[3,15]]
3. x=5, height=12, start: active={10:1,15:1,12:1}, max=15, result=[[2,10],[3,15]]
4. x=7, height=15, end: active={10:1,12:1}, max=12, result=[[2,10],[3,15],[7,12]]
5. x=9, height=10, end: active={12:1}, max=12, result=[[2,10],[3,15],[7,12]]
6. x=12, height=12, end: active={}, max=0, result=[[2,10],[3,15],[7,12],[12,0]]
7. x=15, height=10, start: active={10:1}, max=10, result=[[2,10],[3,15],[7,12],[12,0],[15,10]]
8. x=19, height=8, start: active={10:1,8:1}, max=10, result=[[2,10],[3,15],[7,12],[12,0],[15,10]]
9. x=20, height=10, end: active={8:1}, max=8, result=[[2,10],[3,15],[7,12],[12,0],[15,10],[20,8]]
10. x=24, height=8, end: active={}, max=0, result=[[2,10],[3,15],[7,12],[12,0],[15,10],[20,8],[24,0]]Time and Space Complexity
- **Time Complexity:** O(n log n) where n is the number of buildings
- Sorting events: O(n log n)
- Heap operations: O(log n) per event
- Total: O(n log n)
- **Space Complexity:** O(n) for storing events and active heights
Key Insights
- **Line sweep technique** is perfect for geometric problems involving events
- **Priority queue** efficiently tracks the current maximum height
- **Event-based approach** simplifies complex geometric reasoning
- **Height counting** handles overlapping buildings correctly
Alternative Approaches
i also considered:
- **Divide and conquer** - Split buildings and merge skylines
- **Segment tree** - For range maximum queries
- **Brute force** - Check all possible x-coordinates (inefficient)
The line sweep approach is the most elegant and efficient solution.
Edge Cases to Consider
- **Overlapping buildings** - Multiple buildings at same height
- **Adjacent buildings** - Buildings that touch but don't overlap
- **Single building** - Just one building in the input
- **No buildings** - Empty input
- **Very tall buildings** - Buildings that dominate the skyline
Lessons Learned
this problem taught me:
- How to apply line sweep algorithms to geometric problems
- The importance of event-based processing
- How to use data structures (heaps, hash maps) together
- The power of sorting to simplify complex problems
Conclusion
the skyline problem is a beautiful example of how algorithmic techniques can solve complex geometric problems. the line sweep approach with priority queues is both elegant and efficient.
you can find my complete solution on [leetcode](https://leetcode.com/problems/the-skyline-problem/solutions/6295982/solve-the-skyline-problem-by-1cbyc-qfnm).
---
*this is part of my leetcode problem-solving series. i'm documenting my solutions to help others learn and to track my own progress.*
---
*This is part of my LeetCode problem-solving series. I'm documenting my solutions to help others learn and to track my own progress.*