|
| 1 | +## 题目地址(820.Number Stream to Intervals) |
| 2 | + |
| 3 | +https://binarysearch.com/problems/Number-Stream-to-Intervals |
| 4 | + |
| 5 | +## 题目描述 |
| 6 | + |
| 7 | +``` |
| 8 | +Implement a data structure with the following methods: |
| 9 | +
|
| 10 | +StreamSummary() constructs a new instance. |
| 11 | +add(int val) adds the number val to the instance. |
| 12 | +int[][] get() returns a sorted list of disjoint intervals summarizing the numbers we've seen so far. |
| 13 | +Constraints |
| 14 | +
|
| 15 | +n ≤ 10,000 where n is the number of calls to add |
| 16 | +m ≤ 10,000 where n is the number of calls to get |
| 17 | +Example 1 |
| 18 | +Input |
| 19 | +methods = ["constructor", "add", "add", "add", "add", "get"] |
| 20 | +arguments = [[], [1], [3], [2], [9], []]` |
| 21 | +Output |
| 22 | +[None, None, None, None, None, [ |
| 23 | + [1, 3], |
| 24 | + [9, 9] |
| 25 | +]] |
| 26 | +Explanation |
| 27 | +s = StreamSummary() |
| 28 | +s.add(1) |
| 29 | +s.add(3) |
| 30 | +s.add(2) |
| 31 | +s.add(9) |
| 32 | +s.get() == [[1, 3], [9, 9]] |
| 33 | +Example 2 |
| 34 | +Input |
| 35 | +methods = ["constructor", "add", "add", "add", "add", "get"] |
| 36 | +arguments = [[], [1], [2], [4], [3], []]` |
| 37 | +Output |
| 38 | +[None, None, None, None, None, [ |
| 39 | + [1, 4] |
| 40 | +]] |
| 41 | +Explanation |
| 42 | +s = StreamSummary() |
| 43 | +s.add(1) |
| 44 | +s.add(2) |
| 45 | +s.add(4) |
| 46 | +s.add(3) |
| 47 | +s.get() == [[1, 4]] |
| 48 | +``` |
| 49 | + |
| 50 | +## 前置知识 |
| 51 | + |
| 52 | +- 哈希表 |
| 53 | +- 有序哈希表 |
| 54 | +- 二分法 |
| 55 | + |
| 56 | +## 思路 |
| 57 | + |
| 58 | +这道题是给我们一个数据流。由于是流,因此不是一次性给我们的。题目的意思是每次 add 都会增加一个 [val, val] 的左右闭合的区间。如果 add 的区间**与左边或者右边能够合并**,我们需要将其合并,get 需要返回合并之后的区间总和。 |
| 59 | + |
| 60 | +以题目中的: |
| 61 | + |
| 62 | +```py |
| 63 | +s.add(1) |
| 64 | +s.add(3) |
| 65 | +s.add(2) |
| 66 | +s.add(9) |
| 67 | + |
| 68 | +``` |
| 69 | + |
| 70 | +为例。 |
| 71 | + |
| 72 | +我们分步看一下合并后的区间情况。 |
| 73 | + |
| 74 | +```py |
| 75 | +s.add(1) # [ [1,1] ] |
| 76 | +s.add(3) # [ [1,1], [3,3] ] |
| 77 | +s.add(2) # [ [1,1], [2,2], [3,3] ] 可合并为 [ [1,3] ] |
| 78 | +s.add(9) # [ [1,3], [9,9] ] |
| 79 | +``` |
| 80 | + |
| 81 | +因此这个时候调用 get 会返回 `[ [1,3], [9,9] ]`。 |
| 82 | + |
| 83 | +题目意思就是这样,接下来我们只需要模拟即可。由于每次 add 都需要判断其是否会和前面的区间或者后面的区间进行合并,因此我们可以使用两个哈希表存储。 |
| 84 | + |
| 85 | +- 哈希表 start 其中 start[x] 表示以 x 为区间左端点的区间的右端点,也就是说其表示的是区间 [ x, start[x] ]。 |
| 86 | +- 哈希表 end 其中 end[x] 表示以 x 为区间右端点的区间的左端点,也就是说其表示的是区间 [ end[x], x ]。 |
| 87 | + |
| 88 | +这样 add 的时候就有四种情况: |
| 89 | + |
| 90 | +- 仅和左边区间结合,也就是说 val - 1 在 end 中。此时 [a,val-1],[val+1,b] 可以和 [val,val] 合并为 [a,b] |
| 91 | +- 仅和右边区间结合,也就是说 val + 1 在 start 中.此时 [val+1,b] 可以和 [val,val] 合并为 [val,b] |
| 92 | +- 和左右边区间都结合,也就是说 val - 1 在 end 中 且 val + 1 在 start 中.此时 [a,val-1] 可以和 [val,val] 合并为 [a,val] |
| 93 | +- 不和左右区间结合 |
| 94 | + |
| 95 | +根据上面的四种情况更新 start 和 end 即可。需要注意的是更新了区间(区间合并)之后,需要将原有的区间从哈希表移除,以免影响最终结果。 |
| 96 | + |
| 97 | +由于题目说明了 get 返回值需要是升序排序的,而普通的哈希表是乱序的。因此我们需要: |
| 98 | + |
| 99 | +- get 部分对哈希表进行排序之后再返回 |
| 100 | + |
| 101 | +这种做法 add 时间复杂度为 $O(1)$,get 时间复杂度为 $mlogm$,m 为合并后的区间个数。 |
| 102 | + |
| 103 | +- 使用 SortedDict |
| 104 | + |
| 105 | +由于 SortedDict 内部使用的是平衡树,因此 add 时间复杂度为 $O(logn)$, get 时间复杂度为 $O(m)$,m 为合并后的区间个数。 |
| 106 | + |
| 107 | +这两种方法都可以,大家可以根据 add 和 get 的调用频率以及 m 和 n 的大小关系决定使用哪一种。 |
| 108 | + |
| 109 | +## 代码 |
| 110 | + |
| 111 | +代码支持:Python3 |
| 112 | + |
| 113 | +Python3 Code: |
| 114 | + |
| 115 | +```py |
| 116 | +from sortedcontainers import SortedDict |
| 117 | + |
| 118 | + |
| 119 | +class StreamSummary: |
| 120 | + def __init__(self): |
| 121 | + self.start = SortedDict() |
| 122 | + self.end = SortedDict() |
| 123 | + |
| 124 | + def add(self, val): |
| 125 | + if val - 1 in self.end and val + 1 in self.start: |
| 126 | + # [a, val-1] + [val,val] + [val+1, b] -> [a, b] |
| 127 | + self.end[self.start[val + 1]] = self.end[val - 1] |
| 128 | + self.start[self.end[val - 1]] = self.start[val + 1] |
| 129 | + del self.start[val + 1] |
| 130 | + del self.end[val - 1] |
| 131 | + elif val - 1 in self.end: |
| 132 | + # [a, val -1] + [val, val] -> [a, val] |
| 133 | + self.end[val] = self.end[val - 1] |
| 134 | + self.start[self.end[val]] = val |
| 135 | + del self.end[val - 1] |
| 136 | + elif val + 1 in self.start: |
| 137 | + # [val,val] + [val+1, b] -> [val, b] |
| 138 | + self.start[val] = self.start[val + 1] |
| 139 | + self.end[self.start[val]] = val |
| 140 | + del self.start[val + 1] |
| 141 | + else: |
| 142 | + self.start[val] = val |
| 143 | + self.end[val] = val |
| 144 | + |
| 145 | + def get(self): |
| 146 | + # iterate start or end get same correct answer |
| 147 | + ans = [] |
| 148 | + for s, e in self.start.items(): |
| 149 | + ans.append([s, e]) |
| 150 | + return ans |
| 151 | + |
| 152 | +``` |
| 153 | + |
| 154 | +**复杂度分析** |
| 155 | + |
| 156 | +令 n 为数据流长度,m 为合并后的区间个数。 |
| 157 | + |
| 158 | +- 时间复杂度:add 时间复杂度为 $O(logn)$, get 时间复杂度为 $O(m)$ |
| 159 | +- 空间复杂度:$O(m)$ |
| 160 | + |
| 161 | +力扣的小伙伴可以[关注我](https://leetcode-cn.com/u/fe-lucifer/),这样就会第一时间收到我的动态啦~ |
| 162 | + |
| 163 | +以上就是本文的全部内容了。大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 39K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。 |
0 commit comments