打点计时器的区间合并
问题描述
小明正在设计一台打点计数器,该计数器可以接受多个递增的数字范围,并对这些范围内的每个唯一数字打点。如果多个范围之间有重叠,计数器将合并这些范围并只对每个唯一数字打一次点。小明需要你帮助他计算,在给定的多组数字范围内,计数器会打多少个点。
例如,给定三个数字范围 [1, 4], [7, 10], 和 [3, 5],计数器首先将这些范围合并,变成 [1, 5] 和 [7, 10],然后计算这两个范围内共有多少个唯一数字,即从 1 到 5 有 5 个数字,从 7 到 10 有 4 个数字,共打 9 个点。
思路解析
- 思路比较简单,只要跟着题目第二段走即可
- 首先要将范围合并,避免发生节点的重复计算
- 最后统计合并后的所有线段长度(这里需要注意到的是,开区间线段长度包含首位两头,即计算长度时需要用end-start+1来得到所求长度
解题步骤
1.首先对全部数字范围的起始点进行排序,这样更有利于后续计算,只需要考虑遍历线段的start与已有线段的end大小比较即可(不排序也可以,但是线段两头都需要进行考虑,不利于代码检查)
inputArray.sort(key=lambda x: x[0])
2.创建一个空的list来存储合并后的数据
merged_intervals = []
3.重点步骤,遍历全部数据,先存入第一个数据(用结果list是否为空来判断),后续数据输入时如果与已有数据有重合部分则合并,如果无则作为新的数据加入结果list
for interval in inputArray:
# 如果merged_intervals为空,或者当前区间的起始值大于最后一个合并区间的结束值
if not merged_intervals or interval[0] > merged_intervals[-1][1]:
# 直接将当前区间加入merged_intervals
merged_intervals.append(interval)
else:
# 否则,合并当前区间到merged_intervals的最后一个区间
merged_intervals[-1][1] = max(merged_intervals[-1][1], interval[1])
4.数据合并完之后,开始计数,遍历全部的结果list来获取最终答案(注意这里每组头尾相减后还需要减一),最后返回所求答案
count = 0
for interval in merged_intervals:
count += interval[1] - interval[0] + 1
return count
完整代码
def solution(inputArray):
# 首先对区间进行排序,排序的依据是区间的起始值
inputArray.sort(key=lambda x: x[0])
# 初始化一个列表来存储合并后的区间
merged_intervals = []
# 遍历每个区间
for interval in inputArray:
# 如果merged_intervals为空,或者当前区间的起始值大于最后一个合并区间的结束值
if not merged_intervals or interval[0] > merged_intervals[-1][1]:
# 直接将当前区间加入merged_intervals
merged_intervals.append(interval)
else:
# 否则,合并当前区间到merged_intervals的最后一个区间
merged_intervals[-1][1] = max(merged_intervals[-1][1], interval[1])
# 计算合并后的区间中包含的唯一数字的数量
count = 0
for interval in merged_intervals:
count += interval[1] - interval[0] + 1
return count
if __name__ == "__main__":
# 你可以添加更多测试用例
testArray1 = [[1,4], [7, 10], [3, 5]]
testArray2 = [[1,2], [6, 10], [11, 15]]
print(solution(testArray1) == 9)
print(solution(testArray2) == 12)
额外思考
刚看到这道题的时候,第一个反应是采取线段树来解决(题目要求与性质都比较符合线段树的使用范畴),但由于不会使用python中的线段树,故使用Marscode AI来帮我采用线段树解决该问题,代码如下
class SegmentTree:
def __init__(self, start, end):
self.start = start
self.end = end
self.count = 0
self.left = None
self.right = None
def insert(self, start, end):
if start <= self.start and end >= self.end:
self.count += 1
return
mid = (self.start + self.end) // 2
if start <= mid:
if not self.left:
self.left = SegmentTree(self.start, mid)
self.left.insert(start, min(end, mid))
if end > mid:
if not self.right:
self.right = SegmentTree(mid + 1, self.end)
self.right.insert(max(start, mid + 1), end)
def query(self):
if self.count > 0:
return self.end - self.start + 1
left_count = self.left.query() if self.left else 0
right_count = self.right.query() if self.right else 0
return left_count + right_count
def solution(inputArray):
# 找到所有区间的最小起始值和最大结束值
min_start = min(interval[0] for interval in inputArray)
max_end = max(interval[1] for interval in inputArray)
# 构建线段树
root = SegmentTree(min_start, max_end)
# 插入每个区间
for interval in inputArray:
root.insert(interval[0], interval[1])
# 查询线段树,计算唯一数字的数量
return root.query()
if __name__ == "__main__":
# 你可以添加更多测试用例
testArray1 = [[1,4], [7, 10], [3, 5]]
testArray2 = [[1,2], [6, 10], [11, 15]]
print(solution(testArray1) == 9)
print(solution(testArray2) == 12)
首先,代码过长增加了阅读难度;其次,在数据过多,范围过大的情况下对内存有较高的要求(之前的代码对内存压力较小);最后,该方案使用了多次递归,在大量数据情况下,存在时间花费长,内存占用大的问题。故不推荐使用,具体内容详见代码
总结时刻
要解决这道题,首先需要活用排序,排序后能减少我们很多的逻辑思考;其次对数据进行合并最好是新开一个list,这样可以更清晰的进行分组而且还能保留原有数据进行检查和其他用途。