区间模拟
题目
给你一个由非负整数 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]]
分析
需要通过数字来生成区间,且数字并不是按照大小顺序出现。因此,需要用一个动态结构来维护这样的区间操作。为了方便数据加入,可以通过并查集或者其他方式尽快定位可以加速。
方法一
简单的表记录法,用数组来记录每个值。
时间复杂度O( C),空间复杂度O( C),C = 10001。
class SummaryRanges {
public boolean[] ifnums;
public SummaryRanges() {
ifnums = new boolean[10001];
}
public void addNum(int value) {
ifnums[value] = true;
}
public int[][] getIntervals() {
List<int[]> list = new ArrayList();
int start = -1, end = -1;
for(int i = 0; i < 10000; ++ i){
if(ifnums[i]){
if(start == -1){
start = i;
end = i;
}
else{
end = i;
}
}
else{
if(start != -1){
list.add( new int[]{start,end});
start = -1;
end = -1;
}
}
}
int size = list.size();
return list.toArray(new int[size][2]);
}
}
方法二
并查集来加速搜索过程,优化右边界的搜索。
class SummaryRanges {
public int[] father = new int[10001];
public SummaryRanges() {
Arrays.fill(father,-1);
}
public void union(int x, int y){
if(x >=0 && x <=10000 && father[x] != -1 && father[y] != -1){
int fx = find(x);
int fy = find(y);
if( fx != fy){
father[fx] = fy;
}
}
}
public int find(int x){
while(x != father[x]){
x = father[x];
}
return x;
}
public void addNum(int val) {
if(father[val] == -1){
father[val] = val;
union(val, val + 1);
union(val - 1, val);
}
}
public int[][] getIntervals() {
List<int[]> temp = new ArrayList<>();
for(int i = 0; i < 10000;){
if(father[i] != -1){
int s = i;
int e = find(i);
temp.add(new int[]{s,e});
i = e + 1;
}
else{
++ i;
}
}
return temp.toArray(new int[temp.size()][2]);
}
}
时间复杂度O( C),C为10001,但存在路径压缩,因此会小一些。
空间复杂度O( C),C为temp链表大小。
方法三
并查集优化了右边界的查找,但是左边界仍需要依次遍历,因此可以通过set的方式来记录存在的左边界。
class SummaryRanges {
public int[] father = new int[10001];
public Set<Integer> set;
public SummaryRanges() {
Arrays.fill(father,-1);
set = new TreeSet<>();
}
public void union(int x, int y){
if(x >=0 && x <=10000 && father[x] != -1 && father[y] != -1){
int fx = find(x);
int fy = find(y);
if( fx != fy){
father[fx] = fy;
set.remove(y);
}
}
}
public int find(int x){
while(x != father[x]){
x = father[x];
}
return x;
}
public void addNum(int val) {
if(father[val] == -1){
set.add(val);
father[val] = val;
union(val, val + 1);
union(val - 1, val);
}
}
public int[][] getIntervals() {
List<int[]> temp = new ArrayList<>();
for(int start: set){
int end = find(start);
temp.add(new int[]{start,end});
}
return temp.toArray(new int[temp.size()][2]);
}
}
时间复杂度O(logk)k为左边界数量
空间复杂度O( C ) C为Treeset的大小
方法四
二分法,具体思路就是将左边的遍历过程改为通过二分搜索减少遍历数量,时间复杂度为O(logN)N为10001。相较于并查集慢,因此,不采用该种方式。