1. 问题描述:
给定一个非负整数的数据流输入 a1,a2,…,an,…,将到目前为止看到的数字总结为不相交的区间列表。例如,假设数据流中的整数为 1,3,7,2,6,…,每次的总结为:
[1, 1]
[1, 1], [3, 3]
[1, 1], [3, 3], [7, 7]
[1, 3], [7, 7]
[1, 3], [6, 7]
进阶:
如果有很多合并,并且与数据流的大小相比,不相交区间的数量很小,该怎么办?
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/data-stream-as-disjoint-intervals
2. 思路分析:
分析题目可以知道我们需要对当前的数字合并到已有的区间中,其中主要是分为以下几种区间合并,我们分情况讨论即可:
- 当前的数字x不存在于目前的区间s中,例如当前的区间为s = [[1,2], [4,5]],x = 6则x是不存在与当前的任意一个区间
- 当前的数字x - 1存在于目前的区间s且x + 1不存在于当前的区间s中,例如[[1,3], [7,8]], x = 4这个时候就需要将4合并到区间[1, 3],此时s = [[1,4], [7,8]]
- 当前的数字x - 1不存在于目前的区间s且x + 1存在于当前的区间s中,例如[[1,3], [6,7]], x = 5这个时候就需要将5合并到区间[6,7],此时s = [[1,3], [5,7]]
- 当前的数字x - 1存在于目前的区间s且x + 1存在于当前的区间s中,例如[[1,2], [4,9]],x = 3这个时候需要将两个区间进行合并此时s = [[[1, 9]]
因为使用的是python语言我们可以使用set集合来标记之前已经加入到集合中的数字,这样当前的x在集合中已经存在的时候那么就可以直接返回,也即不进行任何的操作。由于需要进行区间的合并为了减少时间复杂度我们可以将x合并到区间s的时候将所有的区间维护成有序的区间,这样我们就可以使用二分查找找到x合并到区间s的位置,所以这道题目的核心就是二分查找。对于第一种情况我们可以直接将当前的[x, x]插入到区间s中,然后使用sort函数排序即可(或者是使用二分查找到当前需要插入的位置,这里在查找区间右端点的时候如果大于s中最大的那个区间右端点那么直接添加到s后面即可:也即当前插入的位置的值需要严格大于x),对于第二种情况我们可以在区间s中使用二分查找第一个大于等于x - 1的区间右端点值的位置p,我们修改这个区间的右端点的值为x即可,结合上面的例子即可。对于第三种情况也是类似的我们可以在区间s中使用二分查找第一个大于等于x + 1的区间左端点值的位置p,我们修改这个区间的左端点的值为x即x - 1即可。由于第二、三种情况涉及到区间的左端点与右端点所以我们可以写两个二分查找的方法,其中方法都是查找第一个大于等于x的位置,区别在于比较的是区间的左端点还是右端点。对于第四种情况我们需要合并两个区间,我们其实可以查找s中区间右端点第一个大于等于x的位置p,然后修改p - 1的右端点为下一个区间的右端点的值,并且删除掉到当前的对应的第p个区间即可。
3. 代码如下:
from typing import List
class SummaryRanges:
# rec集合来记录之前已经添加过的数字
def __init__(self):
self.rec = set()
# s表示当前的区间
self.s = list()
# 二分查找区间s左端点第一个大于等于x的位置p
def binarySearch_l(self, x: int):
s = self.s
l, r = 0, len(s) - 1
while l < r:
mid = l + r >> 1
if s[mid][0] >= x:
r = mid
else:
l = mid + 1
return l
# 二分查找区间s右端点第一个大于等于x的位置p
def binarySearch(self, x: int):
s = self.s
l, r = 0, len(s) - 1
while l < r:
mid = l + r >> 1
if s[mid][1] >= x:
r = mid
else:
l = mid + 1
return l
# 合并区间的时候总共有四种情况
def addNum(self, val: int) -> None:
rec, s = self.rec, self.s
if val in rec:
return
else:
# 说明当前的val在之前没有出现过
if val - 1 not in rec and val + 1 not in rec:
# 这里还是直接插入然后排序可能好弄一点
s.append([val, val])
s.sort()
elif val - 1 in rec and val + 1 not in rec:
# 需要将val合并到区间右端点, 因为val-1一定存在所以实际上是查找等于val-1的区间
p = self.binarySearch(val - 1)
s[p][1] = val
elif val - 1 not in rec and val + 1 in rec:
# 需要将val合并到区间左端点, 因为val+1一定存在所以实际上是查找等于val+1的区间
p = self.binarySearch_l(val + 1)
s[p][0] = val
else:
# 需要合并两个区间, 并且删除掉其中一个多余的区间
p = self.binarySearch(val)
s[p - 1][1] = s[p][1]
# 删除右边的那个区间即可
s.pop(p)
rec.add(val)
def getIntervals(self) -> List[List[int]]:
return self.s
from typing import List
class SummaryRanges:
def __init__(self):
self.rec = set()
self.s = list()
def binarySearch_l(self, x: int):
s = self.s
l, r = 0, len(s) - 1
# 这里不用像查找右端点那么因为查找区间左端点的时候是一定存在的所以不用加上if判断
while l < r:
mid = l + r >> 1
if s[mid][0] >= x:
r = mid
else:
l = mid + 1
return l
def binarySearch(self, x: int):
s = self.s
l, r = 0, len(s) - 1
# 或者像下面这样当当前的x不在区间s的时候那么添加上一开始的判断这样一开始当x不在区间s的时候可以使用二分查找需要插入的位置
if r >= 0 and x > s[r][1] or not s: return r + 1
while l < r:
mid = l + r >> 1
if s[mid][1] >= x:
r = mid
else:
l = mid + 1
return l
def addNum(self, val: int) -> None:
rec, s = self.rec, self.s
if val in rec:
return
else:
if val - 1 not in rec and val + 1 not in rec:
p = self.binarySearch(val)
if p == len(s):
s.append([val, val])
else:
s.insert(p, [val, val])
elif val - 1 in rec and val + 1 not in rec:
p = self.binarySearch(val - 1)
s[p][1] = val
elif val - 1 not in rec and val + 1 in rec:
p = self.binarySearch_l(val + 1)
s[p][0] = val
else:
p = self.binarySearch(val)
s[p - 1][1] = s[p][1]
s.pop(p)
rec.add(val)
def getIntervals(self) -> List[List[int]]:
return self.s
# if __name__ == '__main__':
# obj = SummaryRanges()
# obj.addNum(1)
# obj.addNum(2)
# obj.addNum(4)
# obj.addNum(3)
# obj.addNum(7)
# print(obj.getIntervals())