[LeetCode] 218. The Skyline Problem 天際線問題


A city's skyline is the outer contour of the silhouette formed by all the buildings in that city when viewed from a distance. Now suppose you are given the locations and height of all the buildings as shown on a cityscape photo (Figure A), write a program to output the skyline formed by these buildings collectively (Figure B).

Buildings Skyline Contour

The geometric information of each building is represented by a triplet of integers [Li, Ri, Hi], where Li and Ri are the x coordinates of the left and right edge of the ith building, respectively, and Hi is its height. It is guaranteed that 0 ≤ Li, Ri ≤ INT_MAX0 < Hi ≤ INT_MAX, and Ri - Li > 0. You may assume all buildings are perfect rectangles grounded on an absolutely flat surface at height 0.

For instance, the dimensions of all buildings in Figure A are recorded as: [ [2 9 10], [3 7 15], [5 12 12], [15 20 10], [19 24 8] ] .

The output is a list of "key points" (red dots in Figure B) in the format of [ [x1,y1], [x2, y2], [x3, y3], ... ] that uniquely defines a skyline. A key point is the left endpoint of a horizontal line segment. Note that the last key point, where the rightmost building ends, is merely used to mark the termination of the skyline, and always has zero height. Also, the ground in between any two adjacent buildings should be considered part of the skyline contour.

For instance, the skyline in Figure B should be represented as:[ [2 10], [3 15], [7 12], [12 0], [15 10], [20 8], [24, 0] ].

Notes:

  • The number of buildings in any input list is guaranteed to be in the range [0, 10000].
  • The input list is already sorted in ascending order by the left x position Li.
  • The output list must be sorted by the x position.
  • There must be no consecutive horizontal lines of equal height in the output skyline. For instance, [...[2 3], [4 5], [7 5], [11 5], [12 7]...] is not acceptable; the three lines of height 5 should be merged into one in the final output as such: [...[2 3], [4 5], [12 7], ...]

Credits:
Special thanks to @stellari for adding this problem, creating these two awesome images and all test cases.

如果按照矩形來處理將會非常麻煩,可以把這些矩形拆成兩個點,一個左上頂點,一個右上頂點,這個題相當於處理2*n個邊的問題,每一個邊有一個x-axis和一個height,把所給的triplet轉換成[x-axis, height],對這些頂點按x-axis排序。然后開始遍歷這些點,用一個最大堆來記錄高度,對於左頂點,將height加入堆中。對於右頂點,從堆中移出height,同時也意味這這個矩形的結束。堆頂是所有頂點中最高的點,是當前圖形的最高位置。只要這個點沒被移出堆,說明這個最高的矩形還沒結束。如果堆頂高度值出現了變化,說明出現了拐點,記錄相應位置到結果中。

具體代碼中,為了在排序后的頂點列表中區分左右頂點,同一個圖形的左右頂點height一個是正數值,一個是負數值。

(1) 自建一個名為Height的數據結構,保存一個building的index和height。約定,當height為負數時表示這個高度為height的building起始於index;height為正時表示這個高度為height的building終止於index。

(2) 對building數組進行處理,每一行[ Li, Ri, Hi ],根據Height的定義,轉換為兩個Height的對象,即,Height(Li, -Hi) 和 Height(Ri, Hi)。 將這兩個對象存入heights這個List中。

(3) 寫個Comparator對heights進行升序排序,首先按照index的大小排序,若index相等,則按height大小排序,以保證一棟建築物的起始節點一定在終止節點之前。

(4) 將heights轉換為結果。使用PriorityQueue對高度值進行暫存。遍歷heights,遇到高度為負值的對象時,表示建築物的起始節點,此時應將這個高度加PriorityQueue。遇到高度為正值的對象時,表示建築物的終止節點,此時應將這個高度從PriorityQueue中除去。且在遍歷的過程中檢查,當前的PriorityQueue的peek()是否與上一個iteration的peek()值(prev)相同,若否,說明出現了拐點,則應在結果中加入[當前對象的index, 當前PriorityQueue的peek()],並更新prev的值。

Java:

class Edge {
	int x;
	int height;
	boolean isStart;
 
	public Edge(int x, int height, boolean isStart) {
		this.x = x;
		this.height = height;
		this.isStart = isStart;
	}
}

public List<int[]> getSkyline(int[][] buildings) {
	List<int[]> result = new ArrayList<int[]>();
 
	if (buildings == null || buildings.length == 0
			|| buildings[0].length == 0) {
		return result;
	}
 
	List<Edge> edges = new ArrayList<Edge>();
 
	// add all left/right edges
	for (int[] building : buildings) {
		Edge startEdge = new Edge(building[0], building[2], true);
		edges.add(startEdge);
		Edge endEdge = new Edge(building[1], building[2], false);
		edges.add(endEdge);
	}
 
	// sort edges 
	Collections.sort(edges, new Comparator<Edge>() {
		public int compare(Edge a, Edge b) {
			if (a.x != b.x)
				return Integer.compare(a.x, b.x);
 
			if (a.isStart && b.isStart) {
				return Integer.compare(b.height, a.height);
			}
 
			if (!a.isStart && !b.isStart) {
				return Integer.compare(a.height, b.height);
			}
 
			return a.isStart ? -1 : 1;
		}
	});
 
	// process edges
	PriorityQueue<Integer> heightHeap = new PriorityQueue<Integer>(10, Collections.reverseOrder());
 
	for (Edge edge : edges) {
		if (edge.isStart) {
			if (heightHeap.isEmpty() || edge.height > heightHeap.peek()) {
				result.add(new int[] { edge.x, edge.height });
			}
			heightHeap.add(edge.height);
		} else {
			heightHeap.remove(edge.height);
 
			if(heightHeap.isEmpty()){
				result.add(new int[] {edge.x, 0});
			}else if(edge.height > heightHeap.peek()){
				result.add(new int[]{edge.x, heightHeap.peek()});
			}
		}
	}
 
	return result;
}  

Java:

public class Solution {
    public List<int[]> getSkyline(int[][] buildings) {
        List<int[]> result = new ArrayList<int[]>();
        if (buildings == null || buildings.length == 0 || buildings[0].length == 0) {
            return result;
        }
        
        List<Height> heights = new ArrayList<Height>();
        for (int[] building : buildings) {
            heights.add(new Height(building[0], -building[2]));
            heights.add(new Height(building[1], building[2]));
        }
        Collections.sort(heights, new Comparator<Height>() {
            @Override
            public int compare(Height h1, Height h2) {
                return h1.index != h2.index ? h1.index - h2.index : h1.height - h2.height;
            }
        });
        
        PriorityQueue<Integer> pq = new PriorityQueue<Integer>(1000, Collections.reverseOrder());
        pq.offer(0);
        int prev = 0;
        for (Height h : heights) {
            if (h.height < 0) {
                pq.offer(-h.height);
            } else {
                pq.remove(h.height);
            }
            int cur = pq.peek();
            if (cur != prev) {
                result.add(new int[]{h.index, cur});
                prev = cur;
            }
        }
        
        return result;
    }
    
    class Height {
        int index;
        int height;
        Height(int index, int height) {
            this.index = index;
            this.height = height;
        }
    }
}

Python:

class Solution(object):
    def getSkyline(self, buildings):
        n = len(buildings)
        points = sorted([(buildings[i][0], buildings[i][2], 's') for i in range(n)] +
                        [(buildings[i][1], buildings[i][2], 'e') for i in range(n)])
        print points
        result, maxHeap = [], []
        for p in points:
            pre_height = - maxHeap[0] if maxHeap else 0
            if p[2] == 's':
                heappush(maxHeap, -p[1])
            else:
                heappop(maxHeap)
            cur_height = - maxHeap[0] if maxHeap else 0

            if p[2] == 's' and p[1] > pre_height:
                result.append([p[0], p[1]])
            elif p[2] == 'e' and p[1] > cur_height:
                result.append([p[0], cur_height])

        return result

Python:

from heapq import heappush, heappop
import functools

class Edge(object):
    def __init__(self, x, height, is_start):
        self.x = x
        self.height = height
        self.is_start = is_start

    def __repr__(self):
        return repr((self.x, self.height, self.is_start))


class Solution1(object):
    def compare(self, edge1, edge2):
        if edge1.x != edge2.x:
            return -1 if edge1.x < edge2.x else 1

        if ((edge1.is_start and edge2.is_start) or
         (not edge1.is_start and not edge2.is_start)):
            return -1 if edge1.height < edge2.height else 1

        return -1 if edge1.is_start else 1

    def getSkyline(self, buildings):
        edges = []
        for building in buildings:
            start_edge = Edge(building[0], building[2], True)
            end_edge = Edge(building[1], building[2], False)
            edges.extend((start_edge, end_edge))
        sorted_edges = sorted(edges, key=functools.cmp_to_key(self.compare))
        print sorted_edges
        maxHeap, result = [], []
        for edge in sorted_edges:
            if edge.is_start:
                if not maxHeap or edge.height > - maxHeap[0]:
                    result.append((edge.x, edge.height))
                heappush(maxHeap, - edge.height)
            else:
                heappop(maxHeap)
                if not maxHeap:
                    result.append((edge.x, 0))
                elif edge.height > - maxHeap[0]:
                    result.append((edge.x, - maxHeap[0]))

        return result

Python:

class MaxHeap:
    def __init__(self, buildings):
        self.buildings = buildings
        self.size = 0
        self.heap = [None] * (2 * len(buildings) + 1)
        self.lineMap = dict()
    def maxLine(self):
        return self.heap[1]
    def insert(self, lineId):
        self.size += 1
        self.heap[self.size] = lineId
        self.lineMap[lineId] = self.size
        self.siftUp(self.size)
    def delete(self, lineId):
        heapIdx = self.lineMap[lineId]
        self.heap[heapIdx] = self.heap[self.size]
        self.lineMap[self.heap[self.size]] = heapIdx
        self.heap[self.size] = None
        del self.lineMap[lineId]
        self.size -= 1
        self.siftDown(heapIdx)
    def siftUp(self, idx):
        while idx > 1 and self.cmp(idx / 2, idx) < 0:
            self.swap(idx / 2, idx)
            idx /= 2
    def siftDown(self, idx):
        while idx * 2 <= self.size:
            nidx = idx * 2
            if idx * 2 + 1 <= self.size and self.cmp(idx * 2 + 1, idx * 2) > 0:
                nidx = idx * 2 + 1
            if self.cmp(nidx, idx) > 0:
                self.swap(nidx, idx)
                idx = nidx
            else:
                break
    def swap(self, a, b):
        la, lb = self.heap[a], self.heap[b]
        self.lineMap[la], self.lineMap[lb] = self.lineMap[lb], self.lineMap[la]
        self.heap[a], self.heap[b] = lb, la
    def cmp(self, a, b):
        return self.buildings[self.heap[a]][2] - self.buildings[self.heap[b]][2]

class Solution:
    def getSkyline(self, buildings):
        size = len(buildings)
        points = sorted([(buildings[x][0], x, 's') for x in range(size)] + 
                        [(buildings[x][1], x, 'e') for x in range(size)])
        maxHeap = MaxHeap(buildings)
        ans = []
        for p in points:
            if p[2] == 's':
                maxHeap.insert(p[1])
            else:
                maxHeap.delete(p[1])
            maxLine = maxHeap.maxLine()
            height = buildings[maxLine][2] if maxLine is not None else 0
            if len(ans) == 0 or ans[-1][0] != p[0]:
                ans.append([p[0], height])
            elif p[2] == 's':
                ans[-1][1] = max(ans[-1][1], height)
            else:
                ans[-1][1] = min(ans[-1][1], height)
            if len(ans) > 1 and ans[-1][1] == ans[-2][1]:
                ans.pop()
        return ans

C++: heap

class Solution {
private:
    enum NODE_TYPE {LEFT, RIGHT};
    struct node {
        int x, y;
        NODE_TYPE type;
        node(int _x, int _y, NODE_TYPE _type) : x(_x), y(_y), type(_type) {}
    };
    
public:
    vector<pair<int, int>> getSkyline(vector<vector<int>>& buildings) {
        vector<node> height;
        for (auto &b : buildings) {
            height.push_back(node(b[0], b[2], LEFT));
            height.push_back(node(b[1], b[2], RIGHT));
        }
        sort(height.begin(), height.end(), [](const node &a, const node &b) {
            if (a.x != b.x) return a.x < b.x;
            else if (a.type == LEFT && b.type == LEFT) return a.y > b.y;
            else if (a.type == RIGHT && b.type == RIGHT) return a.y < b.y;
            else return a.type == LEFT;
        });
        
        priority_queue<int> heap;
        unordered_map<int, int> mp;
        heap.push(0);
        vector<pair<int, int>> res;
        int pre = 0, cur = 0;
        for (auto &h : height) {
            if (h.type == LEFT) {
                heap.push(h.y);
            } else {
                ++mp[h.y];
                while (!heap.empty() && mp[heap.top()] > 0) {
                    --mp[heap.top()];
                    heap.pop();
                }
            }   
            cur = heap.top();
            if (cur != pre) {
                res.push_back({h.x, cur});
                pre = cur;
            }
        }
        return res;
    }
};  

C++: Multiset

class Solution {
public:
    vector<pair<int, int>> getSkyline(vector<vector<int>>& buildings) {
        vector<pair<int, int>> h, res;
        multiset<int> m;
        int pre = 0, cur = 0;
        for (auto &a : buildings) {
            h.push_back({a[0], -a[2]});
            h.push_back({a[1], a[2]});
        }
        sort(h.begin(), h.end());
        m.insert(0);
        for (auto &a : h) {
            if (a.second < 0) m.insert(-a.second);
            else m.erase(m.find(a.second));
            cur = *m.rbegin();
            if (cur != pre) {
                res.push_back({a.first, cur});
                pre = cur;
            }
        }
        return res;
    }
};

 

類似題目:

[LintCode] 131. Building Outline 

 

All LeetCode Questions List 題目匯總


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM