The Skyline Problem

Let’s break down the problem and provide a solution using Python.

  1. Understanding the Problem: We are given a list of buildings, each represented by [left, right, height], where left and right are the x-coordinates of the building’s edges, and height is the building’s height. Our task is to represent the skyline that would be seen from a distance.

  2. Approach: We will scan the buildings from left to right, and we’ll use a max heap to keep track of the heights. When a building ends, we need to remove it from our max heap.

  3. Algorithm:

    • Add all the critical points (both start and end points) to a list and sort them.
    • Traverse through the sorted critical points and for each point:
      • If it’s the start of a building, add the height to a max heap.
      • If it’s the end of a building, remove the height from the max heap.
      • If the current maximum height changes, add the current critical point to the result.

Here’s the Python code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import heapq

class Solution:
    def getSkyline(self, buildings: List[List[int]]) -> List[List[int]]:
        # List to store critical points
        critical_points = []
        for left, right, height in buildings:
            critical_points.append((left, -height)) # Negative height to distinguish starting point
            critical_points.append((right, height))  # Positive height for ending point
        critical_points.sort()

        # Max heap to store current heights
        max_heap = [0]
        # Store previous maximum height to detect changes
        prev_max_height = 0
        # Result to store the skyline
        result = []

        # Traverse critical points
        for x, h in critical_points:
            if h < 0:
                # Starting point: Add height to max heap
                heapq.heappush(max_heap, h)
            else:
                # Ending point: Remove height from max heap
                max_heap.remove(-h)
                heapq.heapify(max_heap)

            # Current maximum height
            curr_max_height = -max_heap[0]

            # If the maximum height changes, add to result
            if curr_max_height != prev_max_height:
                result.append([x, curr_max_height])
                prev_max_height = curr_max_height

        return result

The time complexity of this code is (O(n \log n)) due to sorting and max heap operations, and the space complexity is (O(n)), where (n) is the number of buildings.

Identifying Problem Isomorphism

“The Skyline Problem” has an approximate isomorphism to “850. Rectangle Area II”.

The Skyline Problem involves calculating the outline of a city based on the positions and heights of its buildings, which can be visualized as rectangles in a 2-dimensional space. On the other hand, Rectangle Area II asks for the total area covered by several possibly overlapping 2-D rectangles.

The reason for this selection is that both problems require dealing with rectangles in a 2-D plane, and they require careful consideration of overlapping sections. Both problems need to handle the aggregation of areas or lines with possible overlaps.

This is an approximate isomorphism. While both problems share a context of processing rectangles in a 2D plane, the exact details of what they’re asking for are different. The Skyline Problem is about outlining the “skyline” formed by overlapping rectangles (i.e., finding the outer boundary), whereas Rectangle Area II is about finding the total area covered by possibly overlapping rectangles.

10 Prerequisite LeetCode Problems

“The Skyline Problem” requires intervals and heap data structure. Here are 10 problems:

  1. “Merge Intervals” (LeetCode Problem #56): This problem introduces you to the concept of intervals and how they can be merged.

  2. “Insert Interval” (LeetCode Problem #57): This problem further refines your understanding of handling intervals by adding new ones to a list.

  3. “Meeting Rooms” (LeetCode Problem #252): This problem asks you to determine if a person could attend all meetings based on their schedules, which is a basic interval problem.

  4. “Meeting Rooms II” (LeetCode Problem #253): This problem builds upon the concepts from “Meeting Rooms”, and asks for the minimum number of conference rooms required.

  5. “Find Right Interval” (LeetCode Problem #436): This problem involves finding an interval that begins after another one ends.

  6. “Top K Frequent Elements” (LeetCode Problem #347): This problem introduces you to the concept of a heap and how it can be used to solve problems efficiently.

  7. “Sliding Window Maximum” (LeetCode Problem #239): This problem requires understanding of deque data structure and sliding window concept.

  8. “Kth Largest Element in an Array” (LeetCode Problem #215): This problem requires you to use a heap to maintain the top K elements.

  9. “Queue Reconstruction by Height” (LeetCode Problem #406): This problem introduces you to sorting and the concept of reconstructing a queue based on certain conditions.

  10. “Data Stream as Disjoint Intervals” (LeetCode Problem #352): This problem practices the understanding of handling and merging intervals dynamically.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
from sortedcontainers import SortedList
class Solution:
    def getSkyline(self, buildings: List[List[int]]) -> List[List[int]]:
        if len(buildings) == 0: 
            return []

        buildings.sort(key=lambda v: v[2])
        pos, height = [0], [0]
        for left, right, h in buildings: 
            i = bisect_left(pos, left)
            j = bisect_right(pos, right)
            height[i:j] = [h, height[j-1]]
            pos[i:j] = [left, right]
        print(height, pos)
        res = []
        prev = 0
        for v, h in zip(pos, height): 
            if h != prev:
                res.append([v,h]) 
                prev = h

        return res

Problem Classification

Geometry

Language Agnostic Coding Drills

  1. Understanding the Problem: The problem is asking us to find the skyline of buildings. This means we are given a list of buildings each defined by the left edge, right edge, and height. The output is a list of key points in the skyline where the height changes.

  2. Working with List of Lists: A key data structure used in this problem is a list of lists (the buildings list). Each inner list represents a building’s attributes. Understanding how to index, iterate over, and manipulate these structures is vital.

  3. Sorting Lists: The code first sorts the buildings list by the height of each building. This is an important step as it ensures that buildings are processed in order of their height. Understanding how to implement sorting algorithms or use built-in sorting functions is a key skill here.

  4. Understanding bisect_left and bisect_right functions: These functions from the bisect module in Python return the insertion point for a value in a sorted list to maintain sorted order. If the value already exists in the list, bisect_left will return the position before the existing element, while bisect_right will return the position after the existing element. This is crucial when dealing with ranges, as in this problem.

  5. Manipulating Lists and Indices: The height[i:j] = [h, height[j-1]] and pos[i:j] = [left, right] lines update slices of the height and pos lists. Understanding how to use list slicing and how to update lists in place is important here.

  6. Using zip function: The zip function is used to pair corresponding elements from the pos and height lists. This function returns a zip object, which is an iterator of tuples where the first item in each passed iterator is paired together, and then the second item in each passed iterator are paired together etc.

  7. Identifying Key Points in the Skyline: After processing all the buildings, the final step is to traverse the pos and height lists together to create the skyline. The idea is to record a key point whenever a height change occurs. To do this, we maintain a variable prev to remember the previous height. If the current height is different from the previous one, we record a key point and update prev.

By learning and practicing each of these concepts individually, you will be able to combine them to solve the problem. Remember that practice is key when it comes to coding, so try to solve various problems that make use of these concepts.

Targeted Drills in Python

Drill 1: Working with List of Lists

1
2
3
4
buildings = [[2, 9, 10], [3, 7, 15], [5, 12, 12], [15, 20, 10], [19, 24, 8]]
# print all buildings
for building in buildings:
    print(building)

Drill 2: Sorting Lists

1
2
buildings.sort(key=lambda v: v[2])
print(buildings)

Drill 3: Understanding bisect_left and bisect_right functions

1
2
3
4
5
from bisect import bisect_left, bisect_right

list = [1, 3, 4, 4, 4, 6, 7]
print(bisect_left(list, 4))
print(bisect_right(list, 4))

Drill 4: Manipulating Lists and Indices

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
height = [0, 3, 4, 1]
pos = [1, 3, 7, 9]

# replace height from index 1 to 3 with [10, height[3-1]]
height[1:3] = [10, height[3-1]]
print(height)

# replace pos from index 1 to 3 with [5, pos[3]]
pos[1:3] = [5, pos[3]]
print(pos)

Drill 5: Using zip function

1
2
3
4
5
height = [0, 10, 4, 1]
pos = [1, 5, 7, 9]

for v, h in zip(pos, height):
    print(f'Position: {v}, Height: {h}')

For the final solution, you will need to combine the understanding of these drills. The buildings list should be sorted first, then for each building, you would update the height and pos lists. Finally, you traverse through the height and pos lists to construct the skyline, which involves checking if the current height is different from the previous one, and if so, adding a key point to the skyline.