青训营X豆包MarsCode 技术训练营 | 豆包MarsCode AI 刷题 | 如何计算合并区间内的唯一数字个数

63 阅读6分钟

如何计算合并区间内的唯一数字个数

引言

在算法和数据结构中,区间合并是一个常见的问题。今天,我们来探讨一个与区间合并相关的实际应用:计算多个数字范围内的唯一数字总数

想象一下,你正在设计一台打点计数器。该计数器可以接受多个递增的数字范围,并对这些范围内的每个唯一数字打点。如果多个范围之间有重叠,计数器将合并这些范围,并只对每个唯一数字打一次点。你的任务是计算在给定的多组数字范围内,计数器会打多少个点。

问题描述

给定若干个数字范围,每个范围由两个整数表示,分别为起始值和结束值。计数器需要对这些范围内的每个唯一数字打点。若多个范围有重叠,计数器会合并这些范围,并只对每个数字打一次点。

示例1:

输入:

inputArray = [[1, 4], [7, 10], [3, 5]]

输出:

9

解释:

  • 首先,合并重叠的区间:

    • [1, 4][3, 5] 有重叠,合并为 [1, 5]
    • [7, 10] 保持不变。
  • 然后,计算每个合并后的区间内的数字个数:

    • [1, 5] 包含数字 1, 2, 3, 4, 5,共 5 个数字。
    • [7, 10] 包含数字 7, 8, 9, 10,共 4 个数字。
  • 总共打点次数为 5 + 4 = 9

解题思路

这个问题可以分为两个步骤:

  1. 合并重叠的区间

    • 由于区间可能存在重叠,我们需要先将所有重叠的区间合并,确保每个数字只被统计一次。
  2. 计算合并后区间的数字总数

    • 对于每个合并后的区间,计算其包含的数字个数,最后求和。

步骤一:合并重叠区间

算法:

  • 排序区间:首先,根据每个区间的起始值对区间进行排序。

  • 遍历并合并

    • 初始化一个空的列表 merged 来存储合并后的区间。

    • 遍历排序后的区间:

      • 如果 merged 为空,或者当前区间的起始值大于 merged 列表中最后一个区间的结束值,说明没有重叠,直接将当前区间加入 merged
      • 否则,存在重叠,需要更新 merged 中最后一个区间的结束值为当前区间和最后一个区间结束值的较大者。

示例:

以输入 [[1, 4], [7, 10], [3, 5]] 为例:

  1. 排序后[[1, 4], [3, 5], [7, 10]]

  2. 合并过程

    • 初始化 merged = []

    • 处理 [1, 4]merged = [[1, 4]]

    • 处理 [3, 5]

      • [3, 5] 的起始值 3 小于 merged 最后一个区间 [1, 4] 的结束值 4,存在重叠。
      • 合并为 [1, 5],更新 merged = [[1, 5]]
    • 处理 [7, 10]

      • [7, 10] 的起始值 7 大于 merged 最后一个区间 [1, 5] 的结束值 5,无重叠。
      • 直接加入,merged = [[1, 5], [7, 10]]

步骤二:计算总数字个数

对于每个合并后的区间 [start, end],其包含的数字个数为 end - start + 1

计算:

  • [1, 5] 包含 5 - 1 + 1 = 5 个数字。
  • [7, 10] 包含 10 - 7 + 1 = 4 个数字。
  • 总计 5 + 4 = 9 个数字。

代码实现

以下是使用 Python 实现的完整代码:

def count_unique_numbers(intervals):
    # 排序区间
    intervals.sort(key=lambda x: x[0])
    
    merged = []
    for interval in intervals:
        if not merged:
            merged.append(interval)
        else:
            last = merged[-1]
            if interval[0] <= last[1]:
                # 存在重叠,合并区间
                last[1] = max(last[1], interval[1])
            else:
                # 无重叠,直接添加
                merged.append(interval)
    
    # 计算总数字个数
    total_count = 0
    for interval in merged:
        total_count += interval[1] - interval[0] + 1
    
    return total_count

# 测试
inputArray = [[1, 4], [7, 10], [3, 5]]
print(count_unique_numbers(inputArray))  # 输出:9

时间复杂度分析

  • 排序阶段O(n log n),其中 n 是区间的数量。
  • 合并阶段O(n),遍历一次所有区间。
  • 计算阶段O(k),其中 k 是合并后区间的数量,k <= n

因此,总的时间复杂度为 O(n log n)

更多测试样例

示例2:

输入:

inputArray = [[1, 2], [6, 10], [11, 15]]

输出:

12

解释:

  • 排序后区间:[[1, 2], [6, 10], [11, 15]]

  • 无需合并,直接计算:

    • [1, 2]2 - 1 + 1 = 2
    • [6, 10]10 - 6 + 1 = 5
    • [11, 15]15 - 11 + 1 = 5
  • 总计:2 + 5 + 5 = 12

示例3:

输入:

inputArray = [[1, 3], [2, 6], [8, 10]]

输出:

9

解释:

  • 排序后区间:[[1, 3], [2, 6], [8, 10]]

  • 合并过程:

    • [1, 3][2, 6] 重叠,合并为 [1, 6]
  • 合并后区间:[[1, 6], [8, 10]]

  • 计算数字个数:

    • [1, 6]6 - 1 + 1 = 6
    • [8, 10]10 - 8 + 1 = 3
  • 总计:6 + 3 = 9

总结

通过以上步骤,我们可以高效地计算多个重叠数字范围内的唯一数字总数。关键在于先对区间进行排序,然后合并重叠区间,最后计算合并后区间的数字个数。

这种问题在实际应用中非常常见,例如:

  • 统计IP地址段的总IP数。
  • 计算时间段的总覆盖时间。
  • 资源分配中的冲突检测。

理解并掌握这种算法思想,不仅能解决当前的问题,还能为处理其他类似的问题提供有力的工具。

参考代码

以下是完整的 Python 代码,包含了上述的示例测试:

def count_unique_numbers(intervals):
    # 排序区间
    intervals.sort(key=lambda x: x[0])
    
    merged = []
    for interval in intervals:
        if not merged:
            merged.append(interval)
        else:
            last = merged[-1]
            if interval[0] <= last[1]:
                # 存在重叠,合并区间
                last[1] = max(last[1], interval[1])
            else:
                # 无重叠,直接添加
                merged.append(interval)
    
    # 计算总数字个数
    total_count = 0
    for interval in merged:
        total_count += interval[1] - interval[0] + 1
    
    return total_count

# 测试
test_cases = [
    ([[1, 4], [7, 10], [3, 5]], 9),
    ([[1, 2], [6, 10], [11, 15]], 12),
    ([[1, 3], [2, 6], [8, 10]], 9),
]

for intervals, expected in test_cases:
    result = count_unique_numbers(intervals)
    print(f"Input: {intervals}, Expected Output: {expected}, Actual Output: {result}")
    assert result == expected

输出:

Input: [[1, 4], [7, 10], [3, 5]], Expected Output: 9, Actual Output: 9
Input: [[1, 2], [6, 10], [11, 15]], Expected Output: 12, Actual Output: 12
Input: [[1, 3], [2, 6], [8, 10]], Expected Output: 9, Actual Output: 9

拓展思考

  • 区间的其他操作:除了合并区间,我们还可以对区间进行交集、差集等操作,这些在处理复杂的区间问题时非常有用。
  • 处理更大的数据量:对于非常大的数据集,可以考虑使用更高效的数据结构,如线段树或平衡树,来优化查询和更新操作。
  • 应用场景:区间合并和计算在许多领域都有应用,包括日程安排、网络流量控制、资源管理等。

通过不断地实践和思考,我们可以将这些算法思想应用到更广泛的领域,解决更多复杂的问题。