leetcode 218. The Skyline Problem | 218. 天际线问题(线段树)

x33g5p2x  于2021-11-21 转载在 Kylin  
字(2.8k)|赞(0)|评价(0)|浏览(423)

题目

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

题解

线段树问题,根据左神的思路改编,外加我想到的压缩的 tricks(数字范围太大,实在想不出别的办法了,一开始是直接把过大的数除以某个 scale,但是这样是有损的,无法复原,于是想到了用 map 存压缩前后的映射关系。只要保持压缩前后的数字大小顺序不变,这个压缩就是无损的。)

  1. class Solution {
  2. public static final int N = 1 << 15; // 2^15=32768,考虑到 buildings.length <= 10^4,压缩后总共不超过2*10^4个横坐标
  3. public List<List<Integer>> getSkyline(int[][] buildings) {
  4. SegmentTree segmentTree = new SegmentTree(N);
  5. // 压缩(编码)
  6. // 排序,然后映射,因为不超过10000,所以肯定能映射开
  7. Set<Integer> set = new TreeSet<>();
  8. for (int[] task : buildings) {
  9. set.add(task[0]);
  10. set.add(task[1]);
  11. }
  12. Map<Integer, Integer> decodeMap = new HashMap<>();
  13. Map<Integer, Integer> encodeMap = new HashMap<>();
  14. int cipher = 0;
  15. for (int plain : set) {
  16. cipher += 2; // 用+2而不是+1,目的是留出地面的间隔,避免两个楼挨在一起时,丢失了地面
  17. decodeMap.put(cipher, plain);
  18. encodeMap.put(plain, cipher);
  19. }
  20. for (int[] task : buildings) {
  21. task[0] = encodeMap.get(task[0]);
  22. task[1] = encodeMap.get(task[1]);
  23. }
  24. // 更新线段树(核心)
  25. for (int[] task : buildings) {
  26. segmentTree.update(task[0], task[1], task[2], 0, N, 1); // 把从task[0]到task[1]的位置更新为task[2]
  27. }
  28. segmentTree.flush();
  29. // 打补丁(之前为了方便下标索引,线段树是从1位置开始构建的,现在要把0位置也考虑进去)
  30. segmentTree.max[N - 2] = 0;
  31. segmentTree.max[2 * N - 1] = 0;
  32. for (int[] task : buildings) {
  33. if (task[0] == 0) segmentTree.max[N - 1] = Math.max(segmentTree.max[N - 1], task[2]);
  34. }
  35. // 构造返回值(遍历所有叶子)
  36. List<List<Integer>> result = new ArrayList<>();
  37. for (int i = N - 1; i < 2 * N; i++) {
  38. if (segmentTree.max[i] != segmentTree.max[i - 1]) {
  39. ArrayList<Integer> list = new ArrayList<>();
  40. list.add(segmentTree.max[i - 1] < segmentTree.max[i] ? i - N + 1 : i - N);
  41. list.add(segmentTree.max[i]);
  42. result.add(list);
  43. }
  44. }
  45. // 解码
  46. for (List<Integer> list : result) {
  47. list.set(0, decodeMap.get(list.get(0)));
  48. }
  49. return result;
  50. }
  51. public static class SegmentTree {
  52. private int SIZE;
  53. private int[] max;
  54. private int[] change;
  55. private boolean[] update;
  56. public SegmentTree(int N) {
  57. SIZE = N + 1;
  58. max = new int[SIZE << 2]; // 用来支持脑补概念中,某一个范围的累加和信息
  59. change = new int[SIZE << 2]; // 用来支持脑补概念中,某一个范围有没有更新操作的任务
  60. update = new boolean[SIZE << 2]; // 用来支持脑补概念中,某一个范围是否需要更新(而不是更新成0)
  61. }
  62. // 之前的所有懒增加、懒更新,从父范围发给左右两个子范围,分发策略是什么
  63. private void pushDown(int rt) {
  64. if (update[rt]) {
  65. update[rt << 1] = true;
  66. update[rt << 1 | 1] = true;
  67. change[rt << 1] = change[rt];
  68. change[rt << 1 | 1] = change[rt];
  69. max[rt << 1] = Math.max(max[rt << 1], change[rt]);
  70. max[rt << 1 | 1] = Math.max(max[rt << 1 | 1], change[rt]);
  71. update[rt] = false;
  72. }
  73. }
  74. // 想要把 L~R 所有的值变成 H
  75. // 当前影响 l~r,当前范围信息存在数组的 rt 位置
  76. public void update(int L, int R, int H, int l, int r, int rt) {
  77. if (L <= l && r <= R) {
  78. if (H >= max[rt]) {
  79. update[rt] = true;
  80. change[rt] = H;
  81. max[rt] = H;
  82. }
  83. return;
  84. }
  85. // 当前任务躲不掉,无法懒更新,要往下发一层
  86. int mid = (l + r) >> 1;
  87. pushDown(rt);
  88. if (L <= mid) {
  89. update(L, R, H, l, mid, rt << 1);
  90. }
  91. if (R > mid) {
  92. update(L, R, H, mid + 1, r, rt << 1 | 1);
  93. }
  94. }
  95. // 把所有的懒更新都下放到底部,类似于自底向上的堆排序
  96. public void flush() {
  97. for (int i = max.length - 1; i >= 0; i--) {
  98. if (max[i] == 0) heapify(i);
  99. }
  100. }
  101. public int heapify(int i) {
  102. if (i == 0) return 0;
  103. max[i] = Math.max(max[i], heapify(i / 2));
  104. return max[i];
  105. }
  106. }
  107. }

相关文章