给你一个由非负整数 a1, a2, ..., an
组成的数据流输入,请你将到目前为止看到的数字总结为不相交的区间列表。
实现 SummaryRanges
类:
SummaryRanges()
使用一个空数据流初始化对象。
void addNum(int val)
向数据流中加入整数 val
。
int[][] getIntervals()
以不相交区间 [starti, endi]
的列表形式返回对数据流中整数的总结。
示例:
**输入:**
["SummaryRanges", "addNum", "getIntervals", "addNum", "getIntervals", "addNum", "getIntervals", "addNum", "getIntervals", "addNum", "getIntervals"]
[[], [1], [], [3], [], [7], [], [2], [], [6], []]
**输出:**
[null, null, [[1, 1]], null, [[1, 1], [3, 3]], null, [[1, 1], [3, 3], [7, 7]], null, [[1, 3], [7, 7]], null, [[1, 3], [6, 7]]]
**解释:**
SummaryRanges summaryRanges = new SummaryRanges();
summaryRanges.addNum(1); // arr = [1]
summaryRanges.getIntervals(); // 返回 [[1, 1]]
summaryRanges.addNum(3); // arr = [1, 3]
summaryRanges.getIntervals(); // 返回 [[1, 1], [3, 3]]
summaryRanges.addNum(7); // arr = [1, 3, 7]
summaryRanges.getIntervals(); // 返回 [[1, 1], [3, 3], [7, 7]]
summaryRanges.addNum(2); // arr = [1, 2, 3, 7]
summaryRanges.getIntervals(); // 返回 [[1, 3], [7, 7]]
summaryRanges.addNum(6); // arr = [1, 2, 3, 6, 7]
summaryRanges.getIntervals(); // 返回 [[1, 3], [6, 7]]
提示:
0 <= val <= 104
最多调用 addNum
和 getIntervals
方法 3 * 104
次
进阶: 如果存在大量合并,并且与数据流的大小相比,不相交区间的数量很小,该怎么办?
方法一:使用有序映射维护区间 思路与算法
假设我们使用某一数据结构维护这些不相交的区间,在设计具体的数据结构之前,我们需要先明确 void addNum(int val) 这一操作会使得当前的区间集合发生何种变化:
情况一:如果存在一个区间 $[l, r]$,它完全包含 val,即 $l \leq \textit{val} \leq r$,那么在加入 val 之后,区间集合不会有任何变化;
情况二:如果存在一个区间 $[l, r]$,它的右边界 $r$「紧贴着」val,即 $r + 1 = \textit{val,那么在加入 val 之后,该区间会从 $[l, r]$ 变为 $[l, r+1]$;
情况三:如果存在一个区间 $[l, r]$,它的左边界 $l$「紧贴着」val,即 $l - 1 = \textit{val,那么在加入 val 之后,该区间会从 $[l, r]$ 变为 $[l - 1, r]$;
情况四:如果情况二和情况三同时满足,即存在一个区间 $[l_0, r_0]$ 满足 $r_0+1 = \textit{val,并且存在一个区间 $[l_1, r_1]$ 满足 $l_1-1 = \textit{val,那么在加入 val 之后,这两个区间会合并成一个大区间 $[l_0, r_1]$;
情况五:在上述四种情况均不满足的情况下,val 会单独形成一个新的区间 $[\textit{val}, \textit{val}]$。
根据上面的五种情况,我们需要找到离 val 最近的两个区间。用严谨的语言可以表述为:
对于给定的 val,我们需要找到区间 $[l_0, r_0]$,使得 $l_0$ 是最大的 满足 $l_0 \leq \textit{val 的区间。同时,我们需要找到区间 $[l_1, r_1]$,使得 $l_1$ 是最小的 满足 $l_1 > \textit{val 的区间。
如果我们的数据结构能够快速找到这两个区间,那么上面的五种情况分别对应为:
情况一:$l_0 \leq \textit{val} \leq l_1$;
情况二:$r_0 + 1 = \textit{val;
情况三:$l_1 - 1 = \textit{val;
情况四:$r_0 + 1 = \textit{val 并且 $l_1 - 1 = \textit{val;
情况五:上述情况均不满足。
一种可以找到「最近」区间的数据结构是有序映射。有序映射中的键和值分别表示区间的左边界 $l$ 和右边界 $r$。由于有序映射支持查询「最大的比某个元素小的键」以及「最小的比某个元素大的键」这两个操作,因此我们可以快速地定位区间 $[l_0, r_0]$ 和 $[l_1, r_1]$。
除此之外,对于 int[][] getIntervals() 操作,我们只需要对有序映射进行遍历,将所有的键值对返回即可。
细节
在实际的代码编写中,需要注意 $[l_0, r_0]$ 或 $[l_1, r_1]$ 不存在的情况。
代码
[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 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 class SummaryRanges {private : map<int , int > intervals; public : SummaryRanges () {} void addNum (int val) { auto interval1 = intervals.upper_bound (val); auto interval0 = (interval1 == intervals.begin () ? intervals.end () : prev (interval1)); if (interval0 != intervals.end () && interval0->first <= val && val <= interval0->second) { return ; } else { bool left_aside = (interval0 != intervals.end () && interval0->second + 1 == val); bool right_aside = (interval1 != intervals.end () && interval1->first - 1 == val); if (left_aside && right_aside) { int left = interval0->first, right = interval1->second; intervals.erase (interval0); intervals.erase (interval1); intervals.emplace (left, right); } else if (left_aside) { ++interval0->second; } else if (right_aside) { int right = interval1->second; intervals.erase (interval1); intervals.emplace (val, right); } else { intervals.emplace (val, val); } } } vector<vector<int >> getIntervals () { vector<vector<int >> ans; for (const auto & [left, right]: intervals) { ans.push_back ({left, right}); } return ans; } };
[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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 class SummaryRanges { TreeMap<Integer, Integer> intervals; public SummaryRanges () { intervals = new TreeMap <Integer, Integer>(); } public void addNum (int val) { Map.Entry<Integer, Integer> interval1 = intervals.ceilingEntry(val + 1 ); Map.Entry<Integer, Integer> interval0 = intervals.floorEntry(val); if (interval0 != null && interval0.getKey() <= val && val <= interval0.getValue()) { return ; } else { boolean leftAside = interval0 != null && interval0.getValue() + 1 == val; boolean rightAside = interval1 != null && interval1.getKey() - 1 == val; if (leftAside && rightAside) { int left = interval0.getKey(), right = interval1.getValue(); intervals.remove(interval0.getKey()); intervals.remove(interval1.getKey()); intervals.put(left, right); } else if (leftAside) { intervals.put(interval0.getKey(), interval0.getValue() + 1 ); } else if (rightAside) { int right = interval1.getValue(); intervals.remove(interval1.getKey()); intervals.put(val, right); } else { intervals.put(val, val); } } } public int [][] getIntervals() { int size = intervals.size(); int [][] ans = new int [size][2 ]; int index = 0 ; for (Map.Entry<Integer, Integer> entry : intervals.entrySet()) { int left = entry.getKey(), right = entry.getValue(); ans[index][0 ] = left; ans[index][1 ] = right; ++index; } return ans; } }
[sol1-Python3] 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 from sortedcontainers import SortedDictclass SummaryRanges : def __init__ (self ): self.intervals = SortedDict() def addNum (self, val: int ) -> None : intervals_ = self.intervals keys_ = self.intervals.keys() values_ = self.intervals.values() interval1 = intervals_.bisect_right(val) interval0 = (len (intervals_) if interval1 == 0 else interval1 - 1 ) if interval0 != len (intervals_) and keys_[interval0] <= val <= values_[interval0]: return else : left_aside = (interval0 != len (intervals_) and values_[interval0] + 1 == val) right_aside = (interval1 != len (intervals_) and keys_[interval1] - 1 == val) if left_aside and right_aside: left, right = keys_[interval0], values_[interval1] intervals_.popitem(interval1) intervals_.popitem(interval0) intervals_[left] = right elif left_aside: intervals_[keys_[interval0]] += 1 elif right_aside: right = values_[interval1] intervals_.popitem(interval1) intervals_[val] = right else : intervals_[val] = val def getIntervals (self ) -> List [List [int ]]: return list (self.intervals.items())
[sol1-Golang] 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 type SummaryRanges struct { *redblacktree.Tree } func Constructor () SummaryRanges { return SummaryRanges{redblacktree.NewWithIntComparator()} } func (ranges *SummaryRanges) AddNum(val int ) { interval0, has0 := ranges.Floor(val) if has0 && val <= interval0.Value.(int ) { return } interval1 := ranges.Iterator() if has0 { interval1 = ranges.IteratorAt(interval0) } has1 := interval1.Next() leftAside := has0 && interval0.Value.(int )+1 == val rightAside := has1 && interval1.Key().(int )-1 == val if leftAside && rightAside { interval0.Value = interval1.Value().(int ) ranges.Remove(interval1.Key()) } else if leftAside { interval0.Value = val } else if rightAside { right := interval1.Value().(int ) ranges.Remove(interval1.Key()) ranges.Put(val, right) } else { ranges.Put(val, val) } } func (ranges *SummaryRanges) GetIntervals() [][]int { ans := make ([][]int , 0 , ranges.Size()) for it := ranges.Iterator(); it.Next(); { ans = append (ans, []int {it.Key().(int ), it.Value().(int )}) } return ans }
复杂度分析