Swift 实战:从数据流到不重叠区间的高效转换


文章目录

摘要

在日常开发中,我们经常会遇到这样的需求:从一串不断到来的数据中,实时维护一组"合并好的区间"。比如,日志的连续时间段、用户连续签到天数、实时数据的连续区间等。

LeetCode 第 352 题 Data Stream as Disjoint Intervals 就是这种场景的典型抽象。它要求我们在数据流中动态添加数字,并随时返回当前数字集合的"不重叠有序区间"表示。

本文将用 Swift 从零实现一个高效的 SummaryRanges 类,逐步分析设计思路,并提供一个可直接运行的 Demo。

描述

题目要求我们实现这样一个类:

  1. SummaryRanges():初始化对象,数据流初始为空。
  2. addNum(val):添加一个数字到数据流中。
  3. getIntervals():返回当前数据流中所有数字的合并区间列表,且区间必须有序且不重叠。

例子

假设我们依次调用:

txt 复制代码
addNum(1) → getIntervals() → [[1, 1]]
addNum(3) → getIntervals() → [[1, 1], [3, 3]]
addNum(7) → getIntervals() → [[1, 1], [3, 3], [7, 7]]
addNum(2) → getIntervals() → [[1, 3], [7, 7]]
addNum(6) → getIntervals() → [[1, 3], [6, 7]]

你可以看到,数字进来后如果能与已有区间相连,就会被合并;否则,它会自己成为一个新区间。

题解答案

最直接的解法是:

  • 用一个 有序集合有序数组 存储所有出现过的数字;
  • 每次 getIntervals() 遍历这些数字,按连续性分组形成区间。

但是这样有两个问题:

  1. 每次获取区间都要 O(n) 遍历,调用频繁时性能差;
  2. 插入数字时需要保持有序,普通数组插入 O(n) 会慢。

更好的方法是:

  • 使用 有序字典(SortedDictionary)TreeMap 思路 存储每个区间;
  • addNum 时直接合并到现有区间,避免重复遍历;
  • 这样 getIntervals 可以直接 O(k) 返回区间列表(k 是区间数)。

Swift 没有内置 TreeMap,但可以用 SortedDictionary 的思路,或直接用普通字典 + 手动合并(配合有序 keys)。

题解代码分析

下面是 Swift 代码实现,并且是可运行的 Demo 模块。

swift 复制代码
import Foundation

class SummaryRanges {
    private var intervals: [(Int, Int)] = []
    
    init() {}
    
    func addNum(_ val: Int) {
        // 如果 intervals 为空,直接加
        if intervals.isEmpty {
            intervals.append((val, val))
            return
        }
        
        var newStart = val
        var newEnd = val
        var merged: [(Int, Int)] = []
        var inserted = false
        
        for (start, end) in intervals {
            if end + 1 < newStart {
                // 当前区间完全在新数左边
                merged.append((start, end))
            } else if newEnd + 1 < start {
                // 当前区间完全在新数右边
                if !inserted {
                    merged.append((newStart, newEnd))
                    inserted = true
                }
                merged.append((start, end))
            } else {
                // 有交集或相邻,合并区间
                newStart = min(newStart, start)
                newEnd = max(newEnd, end)
            }
        }
        
        if !inserted {
            merged.append((newStart, newEnd))
        }
        
        intervals = merged
    }
    
    func getIntervals() -> [[Int]] {
        return intervals.map { [$0.0, $0.1] }
    }
}

// Demo
let summaryRanges = SummaryRanges()
summaryRanges.addNum(1)
print(summaryRanges.getIntervals()) // [[1, 1]]
summaryRanges.addNum(3)
print(summaryRanges.getIntervals()) // [[1, 1], [3, 3]]
summaryRanges.addNum(7)
print(summaryRanges.getIntervals()) // [[1, 1], [3, 3], [7, 7]]
summaryRanges.addNum(2)
print(summaryRanges.getIntervals()) // [[1, 3], [7, 7]]
summaryRanges.addNum(6)
print(summaryRanges.getIntervals()) // [[1, 3], [6, 7]]

代码解析

  1. 数据结构选择

    用一个有序的 [(start, end)] 元组数组 intervals 存储区间,每次插入新数字时,按顺序扫描并决定合并还是插入。

  2. 插入逻辑

    • 如果新区间完全在某个区间左侧并且不相连,直接放进合并结果;
    • 如果完全在右侧并且不相连,先放入新数字区间再放现有区间;
    • 如果有交集或相邻,更新 newStartnewEnd 进行合并。
  3. 返回结果
    getIntervals() 直接 O(k) 遍历区间数组并转成二维数组返回。

示例测试及结果

运行上面的 Demo,会输出:

txt 复制代码
[[1, 1]]
[[1, 1], [3, 3]]
[[1, 1], [3, 3], [7, 7]]
[[1, 3], [7, 7]]
[[1, 3], [6, 7]]

这与题目中的预期完全一致。

时间复杂度

  • addNum:O(k),k 为区间数(最坏情况下 k≈n,但通常 k << n);
  • getIntervals:O(k);
  • 对于数据流比较稀疏的情况,性能非常好。

空间复杂度

  • 额外存储 intervals 数组,最多存储 n 个不相交区间,空间复杂度 O(n)。

总结

这道题虽然名字看起来很抽象,但在很多实际场景里都有用武之地,比如:

  • 实时统计用户的连续签到区间;
  • 日志中连续时间片段的合并;
  • 实时视频或音频帧缺失检测。

用 Swift 实现时,我们不需要复杂的平衡树结构,也能通过有序数组+一次扫描的方式做到高效插入和查询。在数据流中区间合并的问题,关键是保持区间有序性和及时合并,这样才能保证后续查询快速、结构清晰。

相关推荐
for_ever_love__2 小时前
UI学习:UISearchController基础了解和应用
学习·ui·ios·objective-c
isyangli_blog4 小时前
OpenDayLight (Carbon 版本) 启动与组件安装
开发语言·php
vb2008114 小时前
FastAPI APIRouter
开发语言·python
Benszen4 小时前
KVM虚拟化解决方案
开发语言·perl
会编程的土豆4 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
東雪木4 小时前
多线程与并发编程 专属复习笔记
java·开发语言·笔记·java面试
杨充4 小时前
1.3 浮点型数据设计灵魂
开发语言·python·算法
噜噜噜阿鲁~4 小时前
python学习笔记 | 11.3、面向对象高级编程-多重继承
java·开发语言
basketball6165 小时前
Go 语言从入门到进阶:4. 数组和MAP使用方法总结
开发语言·后端·golang
春生野草5 小时前
反射、Tomcat执行
java·开发语言