-
Notifications
You must be signed in to change notification settings - Fork 31
/
SegmentSet.ts
377 lines (337 loc) · 10.7 KB
/
SegmentSet.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
/* eslint-disable no-inner-declarations */
/* eslint-disable no-param-reassign */
/* eslint-disable generator-star-spacing */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable class-methods-use-this */
// API:
// insert(left, right) 向区间集合中插入一个区间.
// discard(left, right) 从区间集合中删除一个区间.
// nextStart(x) 返回第一个大于等于x的区间起点.
// prevStart(x) 返回最后一个小于等于x的区间起点.
// ceiling(x) 返回区间内第一个大于等于x的元素.
// floor(x) 返回区间内第一个小于等于x的元素.
// getRange(x) 返回包含x的区间.
// includes(x) 判断x是否在区间集合中.
// includesInterval(left, right) 判断[left, right]是否在区间集合中.
// at(i) 返回第i个区间.
// getAll() 返回所有区间.
// islice(min,max) 返回区间集合中包含在[min,max]区间内的所有区间的迭代器.
// enumerate(min,max,f) 遍历SegmentSet中包含在[min,max]区间内的所有区间范围.
// length SegmentSet中区间的个数.
// count SegmentSet中区间的元素数量.
import { SortedListFast } from '../离线查询/根号分治/SortedList/SortedListFast'
const INF = 2e15
type Interval = { left: number; right: number }
/**
* 管理区间的数据结构.
*
* - 所有区间都是`闭区间` 例如 [1,1] 表示 长为1的区间,起点为1;
* - 有交集的区间会被合并,例如 [1,2]和[2,3]会被合并为[1,3].
*/
class SegmentSet {
private readonly _sl = new SortedListFast<Interval>((a, b) => a.left - b.left)
private readonly _flyWeight = { left: 0, right: 0 }
private _count = 0
/**
* 向区间集合中插入一个闭区间.
*/
insert(left: number, right: number): void {
if (left > right) {
return
}
let it1 = this._sl.bisectRight(this._getFlyWeight(left, INF))
const it2 = this._sl.bisectRight(this._getFlyWeight(right, INF))
if (it1 > 0 && this._sl.at(it1 - 1)!.right >= left) {
it1--
}
if (it1 !== it2) {
const tmp1 = this._sl.at(it1)!.left
left = Math.min(left, tmp1)
const tmp2 = this._sl.at(it2 - 1)!.right
right = Math.max(right, tmp2)
let removed = 0
this._sl.enumerate(
it1,
it2,
v => {
removed += v.right - v.left + 1
},
true
)
this._count -= removed
}
this._sl.add({ left, right })
this._count += right - left + 1
}
/**
* 从区间集合中删除一个闭区间.
*/
discard(left: number, right: number): boolean {
if (left > right) {
return false
}
let it1 = this._sl.bisectRight(this._getFlyWeight(left, INF))
const it2 = this._sl.bisectRight(this._getFlyWeight(right, INF))
if (it1 > 0 && this._sl.at(it1 - 1)!.right >= left) {
it1--
}
if (it1 === it2) {
return false
}
let nl = Math.min(left, this._sl.at(it1)!.left)
let nr = Math.max(right, this._sl.at(it2 - 1)!.right)
let removed = 0
this._sl.enumerate(
it1,
it2,
v => {
removed += v.right - v.left + 1
},
true
)
this._count -= removed
if (nl < left) {
this._sl.add({ left: nl, right: left })
this._count += left - nl + 1
}
if (nr > right) {
this._sl.add({ left: right, right: nr })
this._count += nr - right + 1
}
return true
}
/**
* 返回第一个大于等于x的区间起点.
*/
nextStart(x: number): number | undefined {
const it = this._sl.bisectLeft(this._getFlyWeight(x, -INF))
if (it === this._sl.length) {
return undefined
}
return this._sl.at(it)!.left
}
/**
* 返回最后一个小于等于x的区间起点.
*/
prevStart(x: number): number | undefined {
const it = this._sl.bisectRight(this._getFlyWeight(x, INF)) - 1
if (it < 0) {
return undefined
}
return this._sl.at(it)!.left
}
/**
* 返回区间内第一个大于等于x的元素.
*/
floor(x: number): number | undefined {
const it = this._sl.bisectRight(this._getFlyWeight(x, INF))
if (it === 0) {
return undefined
}
return Math.min(x, this._sl.at(it - 1)!.right)
}
/**
* 返回区间内第一个小于等于x的元素.
*/
ceiling(x: number): number | undefined {
const it = this._sl.bisectRight(this._getFlyWeight(x, INF))
if (it > 0 && this._sl.at(it - 1)!.right >= x) {
return x
}
if (it !== this._sl.length) {
return this._sl.at(it)!.left
}
return undefined
}
/**
* 返回包含x的区间.
*/
getInterval(x: number): Interval | undefined {
const it = this._sl.bisectRight(this._getFlyWeight(x, INF))
if (it === 0 || this._sl.at(it - 1)!.right < x) {
return undefined
}
return this._sl.at(it - 1)!
}
/**
* 判断x是否在区间集合中.
*/
includes(x: number): boolean {
const it = this._sl.bisectRight(this._getFlyWeight(x, INF))
return it > 0 && this._sl.at(it - 1)!.right >= x
}
/**
* 判断区间[left,right]是否在区间集合中.
*/
includesInterval(left: number, right: number): boolean {
if (left > right) {
return false
}
const it1 = this._sl.bisectRight(this._getFlyWeight(left, INF))
if (it1 === 0) {
return false
}
const it2 = this._sl.bisectRight(this._getFlyWeight(right, INF))
if (it1 !== it2) {
return false
}
return this._sl.at(it1 - 1)!.right >= right
}
/**
* 返回第index个区间.
*/
at(index: number): Interval | undefined {
return this._sl.at(index)
}
/**
* 有时需要输出闭区间, insert/remove 时右端点需要加一,getAll 时右端点需要减一.
* ```ts
* seg.insert(left, right + 1)
* seg.remove(left, right + 1)
* seg.getAll().map(v => [v[0], v[1] - 1])
* ```
*/
getAll(): Interval[] {
const res: Interval[] = Array(this._sl.length)
this._sl.forEach((v, i) => {
res[i] = v
})
return res
}
/**
* 返回一个迭代器, 遍历 SegmentSet 中在 `[min,max]` 内的所有区间范围.
*/
*irange(min: number, max: number): IterableIterator<Interval> {
if (min > max) {
return
}
let it = this._sl.bisectRight(this._getFlyWeight(min, INF)) - 1
if (it < 0) it++
const islice = this._sl.slice(it, this._sl.length)
for (const v of islice) {
if (v.left > max) {
return
}
yield { left: Math.max(v.left, min), right: Math.min(v.right, max) }
}
}
/**
* 遍历 SegmentSet 中在 `[min,max]` 内的所有区间范围.
*/
enumerateRange(min: number, max: number, f: (interval: Interval) => void): void {
if (min > max) {
return
}
let it = this._sl.bisectRight(this._getFlyWeight(min, INF)) - 1
if (it < 0) it++
const islice = this._sl.slice(it, this._sl.length)
for (const v of islice) {
if (v.left > max) {
break
}
f({ left: Math.max(v.left, min), right: Math.min(v.right, max) })
}
}
toString(): string {
return `SegmentSet(${this.getAll().join(', ')})`
}
*[Symbol.iterator](): IterableIterator<Interval> {
yield* this._sl
}
get length(): number {
return this._sl.length
}
get count(): number {
return this._count
}
private _getFlyWeight(left: number, right: number): Interval {
this._flyWeight.left = left
this._flyWeight.right = right
return this._flyWeight
}
}
if (require.main === module) {
// https://leetcode.cn/problems/count-integers-in-intervals/description/
// 2276. 统计区间中的整数数目
class CountIntervals {
private readonly _ss = new SegmentSet()
add(left: number, right: number): void {
this._ss.insert(left, right)
}
count(): number {
return this._ss.count
}
}
// 352. 将数据流变为多个不相交区间
// https://leetcode.cn/problems/data-stream-as-disjoint-intervals/
class SummaryRanges {
private readonly _ss = new SegmentSet()
addNum(value: number): void {
this._ss.insert(value, value + 1)
}
getIntervals(): number[][] {
return this._ss.getAll().map(v => [v.left, v.right - 1])
}
}
// https://leetcode.cn/problems/range-module/submissions/
// Range模块/Range 模块
class RangeModule {
private readonly _ss = new SegmentSet()
addRange(left: number, right: number): void {
this._ss.insert(left, right)
}
queryRange(left: number, right: number): boolean {
return this._ss.includesInterval(left, right)
}
removeRange(left: number, right: number): void {
this._ss.discard(left, right)
}
}
// https://leetcode.cn/problems/find-maximal-uncovered-ranges/
function findMaximalUncoveredRanges(n: number, ranges: number[][]): number[][] {
const seg = new SegmentSet()
seg.insert(0, n) // 右边界+1
ranges.forEach(v => {
seg.discard(v[0], v[1] + 1) // 右边界+1
})
return seg.getAll().map(v => [v.left, v.right - 1]) // 右边界-1
}
// 56. 合并区间
// https://leetcode.cn/problems/merge-intervals/description/
function merge(intervals: number[][]): number[][] {
const seg = new SegmentSet()
intervals.forEach(v => {
seg.insert(v[0], v[1])
})
return seg.getAll().map(v => [v.left, v.right])
}
// 57. 插入区间
// https://leetcode.cn/problems/insert-interval/
function insert(intervals: number[][], newInterval: number[]): number[][] {
const seg = new SegmentSet()
intervals.forEach(v => {
seg.insert(v[0], v[1])
})
seg.insert(newInterval[0], newInterval[1])
return seg.getAll().map(v => [v.left, v.right])
}
// 1272. 删除区间
// https://leetcode.cn/problems/remove-interval/
function removeInterval(intervals: number[][], toBeRemoved: number[]): number[][] {
const seg = new SegmentSet()
intervals.forEach(v => {
seg.insert(v[0], v[1])
})
seg.discard(toBeRemoved[0], toBeRemoved[1])
return seg.getAll().map(v => [v.left, v.right])
}
const ss = new SegmentSet()
ss.insert(2, 3)
ss.insert(3, 5)
ss.enumerateRange(2, 6, v => {
console.log(v)
})
console.log(ss.nextStart(4), ss.toString())
console.log(ss.prevStart(99), ss.toString())
}
export { SegmentSet }