题目大意:太复杂了,不太想翻译。对于一丛相互交错的方形建筑,可以由一个二维数组表示各自的轮廓。求从远处观望时看到的
这道题可以通过对离散数据的压缩和线段树解答。
首先将所有端点的x坐标进行压缩(实际上就是对其进行排序和去重,之后用排序的下标代替原来值),映射为0~O(n)个连续整数,其中n为建筑的数目。之所以要压缩数据是为了让线段树使用尽可能少的空间。这里用f表示数据的对应关系,f(x)表示x在有序数组中的下标,f(-1)(i)表示有序数组下标i处的值,f和f(-1)是互逆函数。
之后建立自定义线段树,线段树中区间[x,x].v表示点x+0.5的高度。而[x,y].v=max([x,x].v, [x+1,x+1].v, ... , [y, y].v)。线段树还需要支持设置区间[x,y]的v的最小值的set操作和取v的最大值的get操作。通过值缓存和操作延迟的技术可以保证在O(log2(n))的时间复杂度内可以完成get和set操作。
接下来第一步就是在线段树中建立整个图。遍历所有的建筑[l, r, h],利用线段树的set操作设置区间[f(l), f(r)]的v的最小值为h。
之后遍历整个线段树,将所有有序的区间[x,x]转换为点(f(-1)(x), [x,x].v)加入到结果集合result中。
最后从结果集合result中移除等高相邻的天际线。这是result就是我们要返回的结果了。
说一下时间复杂度,压缩数据(排序和去重)和后期执行的O(n)次f映射和f(-1)映射,分别需要时间复杂度O(nlog2(n))、O(nlog2(n))、O(n)。因此构建映射关系和使用映射关系总共时间复杂度为O(nlog2(n))+O(nlog2(n))+O(n)=O(nlog2(n))。
而线段树部分,建立线段树的时间复杂度为O(n),O(n)次set和get操作需要的时间复杂度分别为O(nlog2(n))、O(nlog2(n)),因此线段树操作占用时间复杂度为O(nlog2(n))。
最后result移除等高相邻的天际线时间复杂度为O(n),因为只需要一次遍历。
综合上面的结论,总的时间复杂度为“建立和使用映射关系” + “线段树操作” + “result移除” = O(nlog2(n)) + O(nlog2(n)) + O(n) = O(nlog2(n))。
空间复杂度压缩数据时有序数组需要占用O(n)的空间复杂度,线段树O(n)的空间复杂度,result同样也是O(n)的空间复杂度,故总空间复杂度为O(n)+O(n)+O(n)=O(n)。
下面给出代码:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 class Solution { 2 public List<int[]> getSkyline(int[][] buildings) { 3 if (buildings.length == 0) { 4 return new ArrayList<>(); 5 } 6 7 CompressMap map = new CompressMap(buildings); 8 SegmenetTree tree = new SegmenetTree(0, map.size() - 1); 9 10 //Find all joint 11 for (int i = 0, bound = buildings.length; i < bound; i++) { 12 tree.setMin(map.get(buildings[i][0]), map.get(buildings[i][1] - 1), buildings[i][2]); 13 } 14 tree.toString(); 15 List<int[]> joints = new ArrayList<>(buildings.length); 16 17 for (int i = tree.getLeftBound() + 1, bound = tree.getRightBound(); i <= bound; i++) { 18 int height = tree.getMax(i, i); 19 int[] joint = new int[]{map.reverse(i), height}; 20 joints.add(joint); 21 } 22 23 List<int[]> result = new ArrayList<>(buildings.length); 24 result.add(joints.get(0)); 25 int lastHeight = joints.get(0)[1]; 26 for (int i = 1, bound = joints.size(); i < bound; i++) { 27 int[] joint = joints.get(i); 28 if (joint[1] == lastHeight) { 29 continue; 30 } 31 lastHeight = joint[1]; 32 result.add(joint); 33 } 34 35 //result.add(new int[]{map.get(tree.getRightBound()), 0}); 36 return result; 37 } 38 39 private static class CompressMap { 40 int[] orderedValues; 41 int length; 42 43 public CompressMap(int[][] buildings) { 44 orderedValues = new int[buildings.length * 4]; 45 for (int i = 0, bound = buildings.length; i < bound; i++) { 46 orderedValues[i << 2] = buildings[i][0]; 47 orderedValues[(i << 2) | 1] = buildings[i][1]; 48 orderedValues[(i << 2) | 2] = buildings[i][0] - 1; 49 orderedValues[(i << 2) | 3] = buildings[i][1] - 1; 50 } 51 52 Arrays.sort(orderedValues); 53 length = 1; 54 for (int i = 1, bound = orderedValues.length; i < bound; i++) { 55 if (orderedValues[i] != orderedValues[i - 1]) { 56 orderedValues[length++] = orderedValues[i]; 57 } 58 } 59 } 60 61 public int get(int val) { 62 return Arrays.binarySearch(orderedValues, 0, length, val); 63 } 64 65 public int reverse(int index) { 66 return orderedValues[index]; 67 } 68 69 public int size() { 70 return length; 71 } 72 } 73 74 private static class SegmenetTree { 75 int left; 76 int right; 77 int[] cacheValues; 78 int[] opMarks; 79 80 public SegmenetTree(int left, int right) { 81 this.left = left; 82 this.right = right; 83 84 int proper = 1; 85 int length = right - left + 1; 86 while (proper < length) { 87 proper <<= 1; 88 } 89 90 cacheValues = new int[proper * 2]; 91 opMarks = new int[proper * 2]; 92 } 93 94 public int getLeftBound() { 95 return left; 96 } 97 98 public int getRightBound() { 99 return right; 100 } 101 102 public void setMin(int from, int to, int val) { 103 setMin(from, to, left, right, 1, val); 104 } 105 106 private void setMin(int from, int to, int curLeft, int curRight, int index, int val) { 107 if (from > curRight || to < curLeft) { 108 return; 109 } 110 111 if (from <= curLeft && to >= curRight) { 112 cacheValues[index] = Math.max(cacheValues[index], val); 113 opMarks[index] = Math.max(opMarks[index], val); 114 return; 115 } 116 117 int mid = (curLeft + curRight) >> 1; 118 setMin(from, to, curLeft, mid, leftIndex(index), val); 119 setMin(from, to, mid + 1, curRight, rightIndex(index), val); 120 cacheValues[index] = Math.max(Math.max(cacheValues[leftIndex(index)], cacheValues[rightIndex(index)]), opMarks[index]); 121 } 122 123 public int getMax(int from, int to) { 124 return getMax(from, to, left, right, 1); 125 } 126 127 private int getMax(int from, int to, int curLeft, int curRight, int index) { 128 if (from <= curLeft && to >= curRight) { 129 return cacheValues[index]; 130 } 131 132 if (from > curRight || to < curLeft) { 133 return 0; 134 } 135 136 int mid = (curLeft + curRight) >> 1; 137 return Math.max(Math.max( 138 getMax(from, to, curLeft, mid, leftIndex(index)), 139 getMax(from, to, mid + 1, curRight, rightIndex(index)) 140 ), opMarks[index]); 141 } 142 143 public int leftIndex(int i) { 144 return (i << 1); 145 } 146 147 public int rightIndex(int i) { 148 return (i << 1) | 1; 149 } 150 151 @Override 152 public String toString() { 153 StringBuilder builder = new StringBuilder(); 154 for (int i = getLeftBound(); i < getRightBound(); i++) { 155 builder.append(getMax(i, i)).append(","); 156 } 157 if (builder.length() > 0) { 158 builder.setLength(builder.length() - 1); 159 } 160 return builder.toString(); 161 } 162 } 163 }