package com.freetymekiyan.algorithms.level.hard; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.TreeMap; /** * 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). * <p> * Figure A: https://leetcode.com/static/images/problemset/skyline1.jpg * Figure B: https://leetcode.com/static/images/problemset/skyline2.jpg * <p> * 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_MAX, 0 < Hi ≤ INT_MAX, and Ri - Li > 0. You may assume all buildings are perfect * rectangles grounded on an absolutely flat surface at height 0. * <p> * 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] ] . * <p> * 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. * <p> * 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] ]. * <p> * Notes: * <p> * 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], ...] * <p> * Company Tags: Microsoft, Google, Facebook, Twitter, Yelp * Tags: Binary Indexed Tree, Segment Tree, Heap, Divide and Conquer */ public class TheSkylineProblem { /** * Heap. Line Sweep Algorithm. * All possible key points are at the edges of rectangle. * Add all top-left and top-right corners to a list. * Set top-left's height to negative to indicate it's left and put them before right points. * Sort the points by x asc, y asc. * Create a tree map with 0,1 in it by default. * Track the height of previous added key point. * Then for each point p in the list: * | If p[1] < 0, left point: * | Add the height count to tree map. * | If p[1] > 0, right point: * | Reduce the height count to tree map. If count is 1, remove the height. * | Get the highest by map.firstKey(). * | If the highest != previous height, a key point found: * | Add it to result. * | Update previous height to current height. * <p> * Tricks: * 1. Store left points height as negative, then they can be distinguished from right points. * 2. Put height 0 count 1 into tree map as a dummy rectangle. 0 is the max height when there is no rectangle. * <p> * https://briangordon.github.io/2014/08/the-skyline-problem.html */ public List<int[]> getSkyline(int[][] buildings) { // Build and sort critical points. List<int[]> cps = new ArrayList<>(); for (int[] b : buildings) { cps.add(new int[]{b[0], -b[2]}); // Left point. Set to negative to benefit sorting. cps.add(new int[]{b[1], b[2]}); // Right point. } Collections.sort(cps, (a, b) -> (a[0] == b[0]) ? a[1] - b[1] : a[0] - b[0]); // A hash heap. Store height and number of rectangles available. Get max height by calling firstKey(). TreeMap<Integer, Integer> maxHeight = new TreeMap<>(Collections.reverseOrder()); // Note reverse order. maxHeight.put(0, 1); // Add a dummy rectangle with height 0. int prevHeight = 0; // Store previous height for comparison later. List<int[]> skyLine = new ArrayList<>(); for (int[] cp : cps) { // Update heap according to left/right point. if (cp[1] < 0) { // Height < 0, left point, add rectangle. Integer cnt = maxHeight.getOrDefault(-cp[1], 0); // Convert negative height to positive. maxHeight.put(-cp[1], cnt + 1); } else { // Height > 0, right point, remove rectangle. Integer cnt = maxHeight.get(cp[1]); if (cnt == 1) { // If only 1 rectangle, remove the height from heap. maxHeight.remove(cp[1]); } else { maxHeight.put(cp[1], cnt - 1); } } // Update result. int currHeight = maxHeight.firstKey(); // Get the highest rectangle. if (prevHeight != currHeight) { // If current max height is the same as prevHeight, not a contour. skyLine.add(new int[]{cp[0], currHeight}); prevHeight = currHeight; } } return skyLine; } }