将数据流变为多个不相交区间
难度:
标签:
题目描述
给你一个由非负整数 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
次
进阶:如果存在大量合并,并且与数据流的大小相比,不相交区间的数量很小,该怎么办?
代码结果
运行时间: 30 ms, 内存: 16.7 MB
/*
* 思路:
* 使用Stream的方式实现区间合并,主要通过Stream来遍历区间和新数字,
* 利用filter和map等方法来处理区间合并逻辑。
*/
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
class SummaryRanges {
private List<int[]> intervals;
public SummaryRanges() {
intervals = new ArrayList<>();
}
public void addNum(int val) {
List<int[]> newIntervals = new ArrayList<>();
intervals = Stream.concat(intervals.stream(), Stream.of(new int[]{val, val}))
.sorted((a, b) -> Integer.compare(a[0], b[0]))
.collect(Collectors.toList());
int[] current = intervals.get(0);
for (int i = 1; i < intervals.size(); i++) {
int[] next = intervals.get(i);
if (current[1] + 1 < next[0]) {
newIntervals.add(current);
current = next;
} else {
current[1] = Math.max(current[1], next[1]);
}
}
newIntervals.add(current);
intervals = newIntervals;
}
public int[][] getIntervals() {
return intervals.toArray(new int[intervals.size()][]);
}
}
解释
方法:
这个题解使用了并查集的思路。主要的过程如下:
1. 初始化一个大小为 MAXNUM 的数组 parent,初始值都为 -1,表示每个数都是一个独立的集合。同时初始化一个有序集合 sortedset 用于存储加入的数字。
2. 当添加一个新的数字 val 时,首先判断 parent[val] 是否为 -1,如果是则将其加入 sortedset,并将 parent[val] 设为 val。然后将 val 与 val-1 和 val+1 进行合并操作。
3. 合并操作使用 union 函数,它首先判断两个数是否都在合法范围内且都已经加入过,然后分别找到它们的根节点,如果根节点不同则将其中一个根节点的父节点设为另一个根节点,相当于将两个集合合并。
4. 当需要获取区间列表时,遍历 sortedset,对于每个数字 s,如果它与前一个区间的右端点重合则跳过,否则找到 s 的根节点 e,将 [s,e] 加入结果列表。
时间复杂度:
addNum: O(logN), getIntervals: O(NlogN), 其中 N 为数据流中的数字个数。
空间复杂度:
O(max(MAXNUM, N))
代码细节讲解
🦆
为什么在`addNum`方法中要同时合并`val`与`val+1`以及`val-1`与`val`,这样做的目的是什么?
▷🦆
在`union`方法中,为何选择将一个根节点的父节点设为另一个根节点,而不是相反,这种选择有何依据?
▷🦆
当`val`已经存在于`sortedset`中时,为什么还需要进行合并操作?存在的话是否会进行重复合并?
▷🦆
在`find`函数中进行路径压缩的目的是什么?它如何帮助优化并查集的性能?
▷相关问题
汇总区间
给定一个 无重复元素 的 有序 整数数组 nums
。
返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说,nums
的每个元素都恰好被某个区间范围所覆盖,并且不存在属于某个范围但不属于 nums
的数字 x
。
列表中的每个区间范围 [a,b]
应该按如下格式输出:
"a->b"
,如果a != b
"a"
,如果a == b
示例 1:
输入:nums = [0,1,2,4,5,7] 输出:["0->2","4->5","7"] 解释:区间范围是: [0,2] --> "0->2" [4,5] --> "4->5" [7,7] --> "7"
示例 2:
输入:nums = [0,2,3,4,6,8,9] 输出:["0","2->4","6","8->9"] 解释:区间范围是: [0,0] --> "0" [2,4] --> "2->4" [6,6] --> "6" [8,9] --> "8->9"
提示:
0 <= nums.length <= 20
-231 <= nums[i] <= 231 - 1
nums
中的所有值都 互不相同nums
按升序排列
寻找右区间
给你一个区间数组 intervals
,其中 intervals[i] = [starti, endi]
,且每个 starti
都 不同 。
区间 i
的 右侧区间 可以记作区间 j
,并满足 startj
>= endi
,且 startj
最小化 。注意 i
可能等于 j
。
返回一个由每个区间 i
的 右侧区间 在 intervals
中对应下标组成的数组。如果某个区间 i
不存在对应的 右侧区间 ,则下标 i
处的值设为 -1
。
示例 1:
输入:intervals = [[1,2]] 输出:[-1] 解释:集合中只有一个区间,所以输出-1。
示例 2:
输入:intervals = [[3,4],[2,3],[1,2]] 输出:[-1,0,1] 解释:对于 [3,4] ,没有满足条件的“右侧”区间。 对于 [2,3] ,区间[3,4]具有最小的“右”起点; 对于 [1,2] ,区间[2,3]具有最小的“右”起点。
示例 3:
输入:intervals = [[1,4],[2,3],[3,4]] 输出:[-1,2,-1] 解释:对于区间 [1,4] 和 [3,4] ,没有满足条件的“右侧”区间。 对于 [2,3] ,区间 [3,4] 有最小的“右”起点。
提示:
1 <= intervals.length <= 2 * 104
intervals[i].length == 2
-106 <= starti <= endi <= 106
- 每个间隔的起点都 不相同
Range 模块
Range模块是跟踪数字范围的模块。设计一个数据结构来跟踪表示为 半开区间 的范围并查询它们。
半开区间 [left, right)
表示所有 left <= x < right
的实数 x
。
实现 RangeModule
类:
RangeModule()
初始化数据结构的对象。void addRange(int left, int right)
添加 半开区间[left, right)
,跟踪该区间中的每个实数。添加与当前跟踪的数字部分重叠的区间时,应当添加在区间[left, right)
中尚未跟踪的任何数字到该区间中。boolean queryRange(int left, int right)
只有在当前正在跟踪区间[left, right)
中的每一个实数时,才返回true
,否则返回false
。void removeRange(int left, int right)
停止跟踪 半开区间[left, right)
中当前正在跟踪的每个实数。
示例 1:
输入 ["RangeModule", "addRange", "removeRange", "queryRange", "queryRange", "queryRange"] [[], [10, 20], [14, 16], [10, 14], [13, 15], [16, 17]] 输出 [null, null, null, true, false, true] 解释 RangeModule rangeModule = new RangeModule(); rangeModule.addRange(10, 20); rangeModule.removeRange(14, 16); rangeModule.queryRange(10, 14); 返回 true (区间 [10, 14) 中的每个数都正在被跟踪) rangeModule.queryRange(13, 15); 返回 false(未跟踪区间 [13, 15) 中像 14, 14.03, 14.17 这样的数字) rangeModule.queryRange(16, 17); 返回 true (尽管执行了删除操作,区间 [16, 17) 中的数字 16 仍然会被跟踪)
提示:
1 <= left < right <= 109
- 在单个测试用例中,对
addRange
、queryRange
和removeRange
的调用总数不超过104
次