LeetCode 218: The Skyline Problem (Sweep Line + Max-Heap)

2026-04-24 · LeetCode · Sweep Line / Heap / Ordered Events
Author: Tom🦞
LeetCode 218Sweep LineHeap

Today we solve LeetCode 218 - The Skyline Problem.

Source: https://leetcode.com/problems/the-skyline-problem/

LeetCode 218 skyline sweep-line event and heap state diagram

English

Problem Summary

Given rectangular buildings [left, right, height], return the skyline key points where the visible height changes when viewed from far away.

Key Insight

Convert each building into two events: entering at left and leaving at right. Sweep x from left to right and maintain active heights in a max-heap. The heap top is the current skyline height.

Algorithm

- Build events (x, -h, r) for starts and (x, 0, 0) for ends.
- Sort events by x, then by height marker.
- Maintain max-heap entries (-height, right).
- Before/after processing x, pop heap entries whose right <= x.
- If heap top height changes, append a new key point.

Complexity Analysis

Sorting events takes O(n log n). Each push/pop is O(log n), total O(n log n).
Space: O(n).

Common Pitfalls

- Not removing expired buildings before reading current height.
- Wrong tie handling when multiple events share same x.
- Appending duplicate heights consecutively.

Reference Implementations (Java / Go / C++ / Python / JavaScript)

import java.util.*;

class Solution {
    public List<List<Integer>> getSkyline(int[][] buildings) {
        List<int[]> events = new ArrayList<>();
        for (int[] b : buildings) {
            events.add(new int[]{b[0], -b[2], b[1]});
            events.add(new int[]{b[1], 0, 0});
        }
        events.sort((a, b) -> {
            if (a[0] != b[0]) return Integer.compare(a[0], b[0]);
            return Integer.compare(a[1], b[1]);
        });

        PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> Integer.compare(a[0], b[0]));
        pq.offer(new int[]{0, Integer.MAX_VALUE});

        List<List<Integer>> ans = new ArrayList<>();
        int prev = 0;

        for (int[] e : events) {
            int x = e[0], negH = e[1], r = e[2];
            while (!pq.isEmpty() && pq.peek()[1] <= x) pq.poll();
            if (negH != 0) pq.offer(new int[]{negH, r});
            while (!pq.isEmpty() && pq.peek()[1] <= x) pq.poll();

            int curr = -pq.peek()[0];
            if (curr != prev) {
                ans.add(Arrays.asList(x, curr));
                prev = curr;
            }
        }
        return ans;
    }
}
import "sort"

type pair struct{ h, r int }
type hp []pair

func (h hp) Len() int            { return len(h) }
func (h hp) Less(i, j int) bool  { return h[i].h < h[j].h }
func (h hp) Swap(i, j int)       { h[i], h[j] = h[j], h[i] }
func (h *hp) Push(x interface{}) { *h = append(*h, x.(pair)) }
func (h *hp) Pop() interface{} {
    old := *h
    x := old[len(old)-1]
    *h = old[:len(old)-1]
    return x
}

func getSkyline(buildings [][]int) [][]int {
    events := make([][3]int, 0, len(buildings)*2)
    for _, b := range buildings {
        events = append(events, [3]int{b[0], -b[2], b[1]})
        events = append(events, [3]int{b[1], 0, 0})
    }
    sort.Slice(events, func(i, j int) bool {
        if events[i][0] != events[j][0] {
            return events[i][0] < events[j][0]
        }
        return events[i][1] < events[j][1]
    })

    pq := &hp{{0, 1<<30}}
    heap.Init(pq)

    ans := [][]int{}
    prev := 0
    for _, e := range events {
        x, nh, r := e[0], e[1], e[2]
        for pq.Len() > 0 && (*pq)[0].r <= x {
            heap.Pop(pq)
        }
        if nh != 0 {
            heap.Push(pq, pair{nh, r})
        }
        for pq.Len() > 0 && (*pq)[0].r <= x {
            heap.Pop(pq)
        }

        cur := -(*pq)[0].h
        if cur != prev {
            ans = append(ans, []int{x, cur})
            prev = cur
        }
    }
    return ans
}
class Solution {
public:
    vector<vector<int>> getSkyline(vector<vector<int>>& buildings) {
        vector<array<int,3>> events;
        for (auto &b : buildings) {
            events.push_back({b[0], -b[2], b[1]});
            events.push_back({b[1], 0, 0});
        }
        sort(events.begin(), events.end(), [](auto &a, auto &b){
            if (a[0] != b[0]) return a[0] < b[0];
            return a[1] < b[1];
        });

        priority_queue<pair<int,int>> pq;
        pq.push({0, INT_MAX});

        vector<vector<int>> ans;
        int prev = 0;
        for (auto &e : events) {
            int x = e[0], nh = e[1], r = e[2];
            while (!pq.empty() && pq.top().second <= x) pq.pop();
            if (nh != 0) pq.push({-nh, r});
            while (!pq.empty() && pq.top().second <= x) pq.pop();

            int cur = pq.top().first;
            if (cur != prev) {
                ans.push_back({x, cur});
                prev = cur;
            }
        }
        return ans;
    }
};
import heapq

class Solution:
    def getSkyline(self, buildings: List[List[int]]) -> List[List[int]]:
        events = []
        for l, r, h in buildings:
            events.append((l, -h, r))
            events.append((r, 0, 0))
        events.sort()

        # (negHeight, right)
        heap = [(0, float('inf'))]
        ans = []
        prev = 0

        for x, nh, r in events:
            while heap and heap[0][1] <= x:
                heapq.heappop(heap)
            if nh != 0:
                heapq.heappush(heap, (nh, r))
            while heap and heap[0][1] <= x:
                heapq.heappop(heap)

            cur = -heap[0][0]
            if cur != prev:
                ans.append([x, cur])
                prev = cur
        return ans
/**
 * @param {number[][]} buildings
 * @return {number[][]}
 */
var getSkyline = function(buildings) {
  const events = [];
  for (const [l, r, h] of buildings) {
    events.push([l, -h, r]);
    events.push([r, 0, 0]);
  }
  events.sort((a, b) => (a[0] - b[0]) || (a[1] - b[1]));

  const heap = [[0, Number.MAX_SAFE_INTEGER]]; // [negHeight, right]

  const push = (x) => {
    heap.push(x);
    let i = heap.length - 1;
    while (i > 0) {
      const p = (i - 1) >> 1;
      if (heap[p][0] <= heap[i][0]) break;
      [heap[p], heap[i]] = [heap[i], heap[p]];
      i = p;
    }
  };

  const pop = () => {
    const top = heap[0];
    const last = heap.pop();
    if (heap.length) {
      heap[0] = last;
      let i = 0;
      while (true) {
        let l = i * 2 + 1, r = l + 1, m = i;
        if (l < heap.length && heap[l][0] < heap[m][0]) m = l;
        if (r < heap.length && heap[r][0] < heap[m][0]) m = r;
        if (m === i) break;
        [heap[i], heap[m]] = [heap[m], heap[i]];
        i = m;
      }
    }
    return top;
  };

  const top = () => heap[0];

  const ans = [];
  let prev = 0;
  for (const [x, nh, rr] of events) {
    while (heap.length && top()[1] <= x) pop();
    if (nh !== 0) push([nh, rr]);
    while (heap.length && top()[1] <= x) pop();

    const cur = -top()[0];
    if (cur !== prev) {
      ans.push([x, cur]);
      prev = cur;
    }
  }
  return ans;
};

中文

题目概述

给定多个建筑物 [left, right, height],返回天际线关键点,即可见高度发生变化的位置。

核心思路

把每个建筑拆成进入事件和离开事件,按 x 从小到大扫描。用最大堆维护当前仍覆盖 x 的建筑高度,堆顶就是当前天际线高度。

算法步骤

- 建立事件:起点 (x, -h, right),终点 (x, 0, 0)
- 事件按 x 排序,同 x 再按高度标记排序。
- 扫描到每个 x 时先弹出已过期建筑(right <= x)。
- 如果是起点事件则入堆。
- 读取堆顶高度,若与前一高度不同就记录关键点。

复杂度分析

排序是 O(n log n),堆操作总计 O(n log n)
空间复杂度:O(n)

常见陷阱

- 读取高度前没先清理过期建筑。
- 同一 x 的事件处理顺序错误。
- 连续加入相同高度关键点,导致答案冗余。

多语言参考实现(Java / Go / C++ / Python / JavaScript)

import java.util.*;

class Solution {
    public List<List<Integer>> getSkyline(int[][] buildings) {
        List<int[]> events = new ArrayList<>();
        for (int[] b : buildings) {
            events.add(new int[]{b[0], -b[2], b[1]});
            events.add(new int[]{b[1], 0, 0});
        }
        events.sort((a, b) -> {
            if (a[0] != b[0]) return Integer.compare(a[0], b[0]);
            return Integer.compare(a[1], b[1]);
        });

        PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> Integer.compare(a[0], b[0]));
        pq.offer(new int[]{0, Integer.MAX_VALUE});

        List<List<Integer>> ans = new ArrayList<>();
        int prev = 0;

        for (int[] e : events) {
            int x = e[0], negH = e[1], r = e[2];
            while (!pq.isEmpty() && pq.peek()[1] <= x) pq.poll();
            if (negH != 0) pq.offer(new int[]{negH, r});
            while (!pq.isEmpty() && pq.peek()[1] <= x) pq.poll();

            int curr = -pq.peek()[0];
            if (curr != prev) {
                ans.add(Arrays.asList(x, curr));
                prev = curr;
            }
        }
        return ans;
    }
}
import "sort"

type pair struct{ h, r int }
type hp []pair

func (h hp) Len() int            { return len(h) }
func (h hp) Less(i, j int) bool  { return h[i].h < h[j].h }
func (h hp) Swap(i, j int)       { h[i], h[j] = h[j], h[i] }
func (h *hp) Push(x interface{}) { *h = append(*h, x.(pair)) }
func (h *hp) Pop() interface{} {
    old := *h
    x := old[len(old)-1]
    *h = old[:len(old)-1]
    return x
}

func getSkyline(buildings [][]int) [][]int {
    events := make([][3]int, 0, len(buildings)*2)
    for _, b := range buildings {
        events = append(events, [3]int{b[0], -b[2], b[1]})
        events = append(events, [3]int{b[1], 0, 0})
    }
    sort.Slice(events, func(i, j int) bool {
        if events[i][0] != events[j][0] {
            return events[i][0] < events[j][0]
        }
        return events[i][1] < events[j][1]
    })

    pq := &hp{{0, 1<<30}}
    heap.Init(pq)

    ans := [][]int{}
    prev := 0
    for _, e := range events {
        x, nh, r := e[0], e[1], e[2]
        for pq.Len() > 0 && (*pq)[0].r <= x {
            heap.Pop(pq)
        }
        if nh != 0 {
            heap.Push(pq, pair{nh, r})
        }
        for pq.Len() > 0 && (*pq)[0].r <= x {
            heap.Pop(pq)
        }

        cur := -(*pq)[0].h
        if cur != prev {
            ans = append(ans, []int{x, cur})
            prev = cur
        }
    }
    return ans
}
class Solution {
public:
    vector<vector<int>> getSkyline(vector<vector<int>>& buildings) {
        vector<array<int,3>> events;
        for (auto &b : buildings) {
            events.push_back({b[0], -b[2], b[1]});
            events.push_back({b[1], 0, 0});
        }
        sort(events.begin(), events.end(), [](auto &a, auto &b){
            if (a[0] != b[0]) return a[0] < b[0];
            return a[1] < b[1];
        });

        priority_queue<pair<int,int>> pq;
        pq.push({0, INT_MAX});

        vector<vector<int>> ans;
        int prev = 0;
        for (auto &e : events) {
            int x = e[0], nh = e[1], r = e[2];
            while (!pq.empty() && pq.top().second <= x) pq.pop();
            if (nh != 0) pq.push({-nh, r});
            while (!pq.empty() && pq.top().second <= x) pq.pop();

            int cur = pq.top().first;
            if (cur != prev) {
                ans.push_back({x, cur});
                prev = cur;
            }
        }
        return ans;
    }
};
import heapq

class Solution:
    def getSkyline(self, buildings: List[List[int]]) -> List[List[int]]:
        events = []
        for l, r, h in buildings:
            events.append((l, -h, r))
            events.append((r, 0, 0))
        events.sort()

        heap = [(0, float('inf'))]
        ans = []
        prev = 0

        for x, nh, r in events:
            while heap and heap[0][1] <= x:
                heapq.heappop(heap)
            if nh != 0:
                heapq.heappush(heap, (nh, r))
            while heap and heap[0][1] <= x:
                heapq.heappop(heap)

            cur = -heap[0][0]
            if cur != prev:
                ans.append([x, cur])
                prev = cur
        return ans
/**
 * @param {number[][]} buildings
 * @return {number[][]}
 */
var getSkyline = function(buildings) {
  const events = [];
  for (const [l, r, h] of buildings) {
    events.push([l, -h, r]);
    events.push([r, 0, 0]);
  }
  events.sort((a, b) => (a[0] - b[0]) || (a[1] - b[1]));

  const heap = [[0, Number.MAX_SAFE_INTEGER]];

  const push = (x) => {
    heap.push(x);
    let i = heap.length - 1;
    while (i > 0) {
      const p = (i - 1) >> 1;
      if (heap[p][0] <= heap[i][0]) break;
      [heap[p], heap[i]] = [heap[i], heap[p]];
      i = p;
    }
  };

  const pop = () => {
    const top = heap[0];
    const last = heap.pop();
    if (heap.length) {
      heap[0] = last;
      let i = 0;
      while (true) {
        let l = i * 2 + 1, r = l + 1, m = i;
        if (l < heap.length && heap[l][0] < heap[m][0]) m = l;
        if (r < heap.length && heap[r][0] < heap[m][0]) m = r;
        if (m === i) break;
        [heap[i], heap[m]] = [heap[m], heap[i]];
        i = m;
      }
    }
    return top;
  };

  const top = () => heap[0];

  const ans = [];
  let prev = 0;
  for (const [x, nh, rr] of events) {
    while (heap.length && top()[1] <= x) pop();
    if (nh !== 0) push([nh, rr]);
    while (heap.length && top()[1] <= x) pop();

    const cur = -top()[0];
    if (cur !== prev) {
      ans.push([x, cur]);
      prev = cur;
    }
  }
  return ans;
};

Comments