https://leetcode.com/problems/booking-concert-tickets-in-groups/
如何把一个问题转化成用线段树解决的问题,这个题是一个很好的示范。


个人解答

  1. class Node:
  2. def __init__(self, v = 0):
  3. self.left = None
  4. self.right = None
  5. self.max = v
  6. self.sum = v
  7. class SegTree:
  8. def __init__(self, n, initVal):
  9. self.initVal = initVal
  10. self.n = n
  11. self.tree = self._build(0, n - 1, initVal)
  12. def _build(self, l, r, initVal):
  13. if l == r:
  14. return Node(initVal)
  15. node = Node()
  16. mid = (l + r) // 2
  17. node.left = self._build(l, mid, initVal)
  18. node.right = self._build(mid + 1, r, initVal)
  19. node.max = max(node.left.max, node.right.max)
  20. node.sum = node.left.sum + node.right.sum
  21. return node
  22. def _decrease(self, node, l, r, i, v):
  23. if l == r:
  24. node.max -= v
  25. node.sum -= v
  26. return
  27. mid = (l + r) // 2
  28. if i <= mid:
  29. self._decrease(node.left, l, mid, i, v)
  30. else:
  31. self._decrease(node.right, mid + 1, r, i, v)
  32. node.max = max(node.left.max, node.right.max)
  33. node.sum = node.left.sum + node.right.sum
  34. def decrease(self, i, v):
  35. self._decrease(self.tree, 0, self.n - 1, i, v)
  36. def _queryAvaliable(self, node, l, r, target, maxRow):
  37. if l > maxRow or node.max < target:
  38. return []
  39. if l == r:
  40. # 要返回所在行以及当前行第一个空的index
  41. return [l, self.initVal - node.max]
  42. mid = (l + r) // 2
  43. if node.left.max >= target:
  44. return self._queryAvaliable(node.left, l, mid, target, maxRow)
  45. return self._queryAvaliable(node.right, mid + 1, r, target, maxRow)
  46. def queryAvaliable(self, target, maxRow):
  47. return self._queryAvaliable(self.tree, 0, self.n - 1, target, maxRow)
  48. def _querySum(self, node, l, r, maxRow):
  49. if l > maxRow:
  50. return 0
  51. # 区间小于要请求的区间,直接返回!
  52. if r <= maxRow or l == r:
  53. return node.sum
  54. mid = (l + r) // 2
  55. return self._querySum(node.left, l, mid, maxRow) + self._querySum(node.right, mid + 1, r, maxRow)
  56. def querySum(self, maxRow):
  57. return self._querySum(self.tree, 0, self.n - 1, maxRow)
  58. class BookMyShow:
  59. def __init__(self, n: int, m: int):
  60. self.tree = SegTree(n, m)
  61. # 记录每行剩下多少,以及第一个remian > 0的行,用来scatter的时候,选取合适的位置更新
  62. self.remain = [m] * n
  63. self.n = n
  64. self.remainIdx = 0
  65. def gather(self, k: int, maxRow: int) -> List[int]:
  66. res = self.tree.queryAvaliable(k, maxRow)
  67. if res and res[0] >= 0:
  68. self.tree.decrease(res[0], k)
  69. self.remain[res[0]] -= k
  70. return res
  71. def scatter(self, k: int, maxRow: int) -> bool:
  72. if self.tree.querySum(maxRow) < k:
  73. return False
  74. # 需要从最小的地方开始更新
  75. i = self.remainIdx
  76. while k:
  77. if k >= self.remain[i]:
  78. # remain为0
  79. self.tree.decrease(i, self.remain[i])
  80. k -= self.remain[i]
  81. self.remain[i] = 0
  82. i += 1
  83. else:
  84. self.tree.decrease(i, k)
  85. self.remain[i] -= k
  86. k = 0
  87. self.remainIdx = i
  88. return True
  89. # Your BookMyShow object will be instantiated and called as such:
  90. # obj = BookMyShow(n, m)
  91. # param_1 = obj.gather(k,maxRow)
  92. # param_2 = obj.scatter(k,maxRow)

题目分析

问题转化

本题看似是两个互不相干的操作,并不是常见的线段树查询/修改的典型模式。但仔细一看,不管是scatter还是gatter,都对应了两个行为:

  • 查询一个符合条件的区间
  • 修改一个/多个值

涉及到反复的修改和查询,线段树很适合了。
再想一想怎么利用区间状态:

  • gather需要一个连续的空间,其实我们要找到剩余空间大于给定值的行,那么区间信息可以存区间里的最大remain,这样可以快速提前拒绝
  • cluster可以分散,但题目的要求可以得知,分配是连续的,也就是连续区间的sum满足即可,那么区间也需要记录sum值

至此区间的值都记录好,接下来的修改和查询就比较自然了。

线段树

线段树的实现,其实不用想着太多的实现trick,就记住核心的思想,然后老老实实用指针实现就可以。

  • 叶节点存具体的值,根节点存区间的值,不管是max/min/sum
  • 每次查询/更新都是从root开始,二分处理
  • 注意提前返回,如querySum的时候,根据区间判断是否可以直接返回