```package com.freetymekiyan.algorithms.level.hard;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.PriorityQueue;
import java.util.TreeMap;

/**
* 218. The Skyline Problem
* <p>
* 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>
* Tags: Binary Indexed Tree, Segment Tree, Heap, Divide and Conquer
*/
public class TheSkylineProblem {

/**
* My own implementation with max heap.
* The candidates are sorted by x asc, y desc.
* For each candidate point:
* | If it's a left point, it's a start of a segment.
* |   Add the height to heap.
* |   If the height is higher than previous height, add it as key point and update previous height.
* | If it's a right point, it's an end of a segment.
* |   Remove the height from heap. Now the height may change.
* |   If there is no segment, add (x, 0) to key points, update previous height as 0.
* |   If there are more segments, add (x, highest) to key points, update previous height as highest.
* The logic above can further simplify to: 1. Update heap. 2. If height changes, add key points and update height.
* If height doesn't change, of course there is no new key points.
* Return a list of key points.
*/
public List<int[]> getSkyline(int[][] buildings) {
List<int[]> corners = new ArrayList<>(buildings.length * 2);
for (int[] b : buildings) {
}
corners.sort((a, b) -> a == b ? b - a : a - b);

PriorityQueue<Integer> pq = new PriorityQueue<>(Collections.reverseOrder()); // Max heap.
pq.offer(0); // Add 0 as minimum height so that we won't need to check whether heap is empty when we remove a height.
List<int[]> keyPoints = new ArrayList<>();
int height = 0;
for (int[] c : corners) {
if (c > 0) { // Top-left. Add a segment height to heap.
pq.offer(c);
} else { // Top-right. Remove a segment height from heap.
pq.remove(-c); // This is O(n). Can be faster with TreeMap.
}
if (pq.peek() != height) { // If the height changes, that means a key point.
height = pq.peek();
}
}
return keyPoints;
}

/**
* 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 < 0, left point:
* |   Add the height count to tree map.
* | If p > 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
* <p>
* The use of tree map makes me feel a bit unnecessary, since priority queue or heap can contain duplicates.
* A priority queue or max heap is enough.
* The only upside is that TreeMap get/put in O(logn). Priority queue removes a specific value in O(n).
* Because it scans through all values to find the one to remove.
*/
public List<int[]> getSkyline2(int[][] buildings) {
// Build and sort critical points.
List<int[]> corners = new ArrayList<>();
for (int[] b : buildings) {
corners.add(new int[]{b, -b}); // Left point. Set to negative to benefit sorting.
corners.add(new int[]{b, b}); // Right point.
}
Collections.sort(corners, (a, b) -> (a == b) ? a - b : a - b);
// A hash heap. Store height and number of rectangles available. Get max height by calling firstKey().
TreeMap<Integer, Integer> heights = new TreeMap<>(Collections.reverseOrder()); // Note reverse order, max heap.
heights.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[] c : corners) {
// Update heap according to left/right point.
if (c < 0) { // Height < 0, left point, add rectangle.
heights.merge(-c, 1, (oldValue, newValue) -> oldValue + newValue);
} else { // Height > 0, right point, remove rectangle.
heights.merge(c, 1, (oldValue, newValue) -> oldValue.equals(newValue) ? null : oldValue - newValue);
}
if (prevHeight != heights.firstKey()) { // If current max height is the same as prevHeight, not a contour.