给你区间的 空 集,请你设计并实现满足要求的数据结构:
新增: 添加一个区间到这个区间集合中。
统计: 计算出现在 至少一个 区间中的整数个数。
实现 CountIntervals
类:
CountIntervals()
使用区间的空集初始化对象
void add(int left, int right)
添加区间 [left, right]
到区间集合之中。
int count()
返回出现在 至少一个 区间中的整数个数。
注意: 区间 [left, right]
表示满足 left <= x <= right
的所有整数 x
。
示例 1:
**输入**
["CountIntervals", "add", "add", "count", "add", "count"]
[[], [2, 3], [7, 10], [], [5, 8], []]
**输出**
[null, null, null, 6, null, 8]
**解释**
CountIntervals countIntervals = new CountIntervals(); // 用一个区间空集初始化对象
countIntervals.add(2, 3); // 将 [2, 3] 添加到区间集合中
countIntervals.add(7, 10); // 将 [7, 10] 添加到区间集合中
countIntervals.count(); // 返回 6
// 整数 2 和 3 出现在区间 [2, 3] 中
// 整数 7、8、9、10 出现在区间 [7, 10] 中
countIntervals.add(5, 8); // 将 [5, 8] 添加到区间集合中
countIntervals.count(); // 返回 8
// 整数 2 和 3 出现在区间 [2, 3] 中
// 整数 5 和 6 出现在区间 [5, 8] 中
// 整数 7 和 8 出现在区间 [5, 8] 和区间 [7, 10] 中
// 整数 9 和 10 出现在区间 [7, 10] 中
提示:
1 <= left <= right <= 109
最多调用 add
和 count
方法 总计 105
次
调用 count
方法至少一次
思路同 715. Range 模块 。
方法一:珂朵莉树 用一颗平衡树维护若干个不相交的区间,每次 add(left,right)
时,删除被该区间覆盖到的区间(部分覆盖也算),然后将这些区间与 [\textit{left},\textit{right}] 合并成一个新的大区间,插入平衡树中。
代码实现时,为方便找到第一个被 [\textit{left},\textit{right}] 覆盖到的区间,我们可以用平衡树的 key 存区间右端点,value 存区间左端点。我们要找的就是第一个 key}\ge\textit{left 的区间。
复杂度分析
时间复杂度:每个区间至多被添加删除各一次,因此 add
操作是均摊 O(\log n) 的,这里 n 是 add
的次数。
空间复杂度:O(n)。
[sol2-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from sortedcontainers import SortedDictclass CountIntervals : def __init__ (self ): self.d = SortedDict() self.cnt = 0 def add (self, left: int , right: int ) -> None : i = self.d.bisect_left(left) while i < len (self.d) and self.d.values()[i] <= right: r, l = self.d.items()[i] left = min (left, l) right = max (right, r) self.cnt -= r - l + 1 self.d.popitem(i) self.cnt += right - left + 1 self.d[right] = left def count (self ) -> int : return self.cnt
[sol2-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class CountIntervals { TreeMap<Integer, Integer> m = new TreeMap <>(); int cnt; public CountIntervals () {} public void add (int left, int right) { for (var e = m.ceilingEntry(left); e != null && e.getValue() <= right; e = m.ceilingEntry(left)) { int l = e.getValue(), r = e.getKey(); left = Math.min(left, l); right = Math.max(right, r); cnt -= r - l + 1 ; m.remove(r); } cnt += right - left + 1 ; m.put(right, left); } public int count () { return cnt; } }
[sol2-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class CountIntervals { map<int , int > m; int cnt = 0 ; public : CountIntervals () {} void add (int left, int right) { for (auto it = m.lower_bound (left); it != m.end () && it->second <= right; m.erase (it++)) { int l = it->second, r = it->first; left = min (left, l); right = max (right, r); cnt -= r - l + 1 ; } cnt += right - left + 1 ; m[right] = left; } int count () { return cnt; } };
[sol2-Go] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 type CountIntervals struct { *redblacktree.Tree cnt int } func Constructor () CountIntervals { return CountIntervals{redblacktree.NewWithIntComparator(), 0 } } func (t *CountIntervals) Add(left, right int ) { for node, _ := t.Ceiling(left); node != nil && node.Value.(int ) <= right; node, _ = t.Ceiling(left) { l, r := node.Value.(int ), node.Key.(int ) if l < left { left = l } if r > right { right = r } t.cnt -= r - l + 1 t.Remove(r) } t.cnt += right - left + 1 t.Put(right, left) } func (t *CountIntervals) Count() int { return t.cnt }
方法二:动态开点线段树 原理见 这篇文章 。完整的动态开点线段树模板见我的 算法竞赛模板库 。
对于本题来说,线段树的每个节点可以保存对应范围的左右端点 l 和 r,以及范围内 add
过的整数个数 cnt。
代码实现时,无需记录 lazy tag,这是因为被覆盖的范围无需再次覆盖,因此若 cnt 等于范围的长度 r-l+1,则可直接返回。
[sol1-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class CountIntervals : __slots__ = 'left' , 'right' , 'l' , 'r' , 'cnt' def __init__ (self, l=1 , r=10 ** 9 ): self.left = self.right = None self.l, self.r, self.cnt = l, r, 0 def add (self, l: int , r: int ) -> None : if self.cnt == self.r - self.l + 1 : return if l <= self.l and self.r <= r: self.cnt = self.r - self.l + 1 return mid = (self.l + self.r) // 2 if self.left is None : self.left = CountIntervals(self.l, mid) if self.right is None : self.right = CountIntervals(mid + 1 , self.r) if l <= mid: self.left.add(l, r) if mid < r: self.right.add(l, r) self.cnt = self.left.cnt + self.right.cnt def count (self ) -> int : return self.cnt
[sol1-Java] 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 class CountIntervals { CountIntervals left, right; int l, r, cnt; public CountIntervals () { l = 1 ; r = (int ) 1e9 ; } CountIntervals(int l, int r) { this .l = l; this .r = r; } public void add (int L, int R) { if (cnt == r - l + 1 ) return ; if (L <= l && r <= R) { cnt = r - l + 1 ; return ; } int mid = (l + r) / 2 ; if (left == null ) left = new CountIntervals (l, mid); if (right == null ) right = new CountIntervals (mid + 1 , r); if (L <= mid) left.add(L, R); if (mid < R) right.add(L, R); cnt = left.cnt + right.cnt; } public int count () { return cnt; } }
[sol1-C++] 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 class CountIntervals { CountIntervals *left = nullptr , *right = nullptr ; int l, r, cnt = 0 ; public : CountIntervals () : l (1 ), r (1e9 ) {} CountIntervals (int l, int r) : l (l), r (r) {} void add (int L, int R) { if (cnt == r - l + 1 ) return ; if (L <= l && r <= R) { cnt = r - l + 1 ; return ; } int mid = (l + r) / 2 ; if (left == nullptr ) left = new CountIntervals (l, mid); if (right == nullptr ) right = new CountIntervals (mid + 1 , r); if (L <= mid) left->add (L, R); if (mid < R) right->add (L, R); cnt = left->cnt + right->cnt; } int count () { return cnt; } };
[sol1-Go] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 type CountIntervals struct { left, right *CountIntervals l, r, cnt int } func Constructor () CountIntervals { return CountIntervals{l: 1 , r: 1e9 } }func (o *CountIntervals) Add(l, r int ) { if o.cnt == o.r-o.l+1 { return } if l <= o.l && o.r <= r { o.cnt = o.r - o.l + 1 return } mid := (o.l + o.r) >> 1 if o.left == nil { o.left = &CountIntervals{l: o.l, r: mid} } if o.right == nil { o.right = &CountIntervals{l: mid + 1 , r: o.r} } if l <= mid { o.left.Add(l, r)} if mid < r { o.right.Add(l, r) } o.cnt = o.left.cnt + o.right.cnt } func (o *CountIntervals) Count() int { return o.cnt }