尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

LeetCode-Go 题解:1656. Design an Ordered Stream(有序流)设计与 Go 实现详解

LeetCode-Go 题解:1656. Design an Ordered Stream(有序流)设计与 Go 实现详解 LeetCode-Go 题解1656. Design an Ordered Stream有序流设计与 Go 实现详解【免费下载链接】LeetCode-Go✅ Solutions to LeetCode by Go, 100% test coverage, runtime beats 100% | LeetCode 题解项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go导读本篇基于 LeetCode-Go 仓库中 1656. Design-an-Ordered-Stream 题目文档 及其配套源码展开深入讲解 LeetCode 第 1656 题「有序流Design an Ordered Stream」的数据结构设计思路与 Go 语言实现。本题考察的是如何用最少的空间与时间在乱序插入的前提下按 ID 递增顺序返回连续数据块其核心是维护一个游标指针ptr是面试中高频出现的指针 模拟类设计题。读完本文你将掌握该题的完整解法、边界条件处理、源码级实现细节以及如何通过仓库中的测试用例验证正确性。一、题目理解在乱序插入中保持有序输出LeetCode-Go 仓库的 README 文档 对题目的原始描述如下英文原题There is a stream ofn(id, value)pairs arriving in anarbitraryorder, whereidis an integer between1andnandvalueis a string. No two pairs have the sameid.Design a stream that returns the values inincreasing order of their IDsby returning achunk(list) of values after each insertion. The concatenation of all thechunksshould result in a list of the sorted values.简单翻译系统会以任意顺序接收n个(id, value)数据对其中id是1到n之间的整数value是字符串且所有id互不相同。要求设计一个有序流Ordered Stream每次插入一个数据对后返回一个数据块chunk所有 chunk 按返回顺序拼接起来恰好等于按 ID 升序排列的完整值列表。1.1 需要实现的接口根据题目要求需要实现OrderedStream类方法签名行为说明构造器OrderedStream(int n)构造一个能接收n个值的流并将当前指针ptr设为1插入String[] insert(int id, String value)存储新的(id, value)对。若流中存在id ptr的数据对则从ptr开始找出最长的 ID 连续递增序列按顺序返回这些 ID 关联的值列表并将ptr更新为最后一个返回 ID 1否则返回空列表1.2 核心机制指针 ptr 与最长连续递增序列理解本题的关键在于两个概念指针ptr始终指向当前期望返回的下一个 ID。初始值为1因为 ID 从 1 开始。返回规则每次insert后只有当ptr位置的数据已经就绪即刚插入的id ptr或者ptr位置早已有值才从ptr开始向后收集连续已填充的 ID收集到第一个空位为止这一串值构成返回的 chunk然后把ptr推进到空位处。换言之返回条件不是插入哪个就返回哪个而是只返回从当前缺口ptr开始的连续段。这保证了所有 chunk 依次拼接后天然有序。二、官方示例逐步拆解文档给出了完整示例。设n 5初始ptr 1依次执行 5 次插入Input [OrderedStream, insert, insert, insert, insert, insert] [[5], [3, ccccc], [1, aaaaa], [2, bbbbb], [5, eeeee], [4, ddddd]] Output [null, [], [aaaaa], [bbbbb, ccccc], [], [ddddd, eeeee]]逐行解读如下os.insert(3, ccccc)→ 返回[]写入位置 3。此时ptr 1位置 1 为空不满足返回条件返回空列表。os.insert(1, aaaaa)→ 返回[aaaaa]写入位置 1恰好id ptr。从位置 1 收集aaaaa就绪位置 2 为空停止。chunk 为[aaaaa]ptr更新为2。os.insert(2, bbbbb)→ 返回[bbbbb, ccccc]写入位置 2。此时stream[ptr]即位置 2已有值。从位置 2 连续收集bbbbb位置 2、ccccc位置 3就绪位置 4 为空停止。chunk 为[bbbbb, ccccc]ptr更新为4。os.insert(5, eeeee)→ 返回[]写入位置 5。ptr 4位置 4 为空返回空列表。os.insert(4, ddddd)→ 返回[ddddd, eeeee]写入位置 4恰好id ptr。从位置 4 连续收集ddddd位置 4、eeeee位置 5就绪位置 6 超出容量停止。chunk 为[ddddd, eeeee]。验证拼接结果[] [aaaaa] [bbbbb, ccccc] [] [ddddd, eeeee] [aaaaa, bbbbb, ccccc, ddddd, eeeee]正好是按 ID 升序的完整值序列与题目预期一致。三、约束条件与设计取舍文档列出的约束条件直接决定了实现策略约束值对实现的影响数据规模1 n 1000规模很小可以直接用长度n1的切片模拟ID 范围1 id nID 与数组下标一一对应天然支持 O(1) 随机写入value 形式value.length 5仅含小写字母只用字符串存储无需额外结构唯一性每次insert的id唯一无需处理覆盖写调用次数恰好调用n次insert最终必然填满整个流由于n ≤ 1000且每次insert最多向后扫描n个位置整体最坏时间复杂度为 O(n²)共 n 次插入 × 每次最坏扫描 O(n)在题目规模下完全可行。空间复杂度为 O(n)存储 n 个 value 加上常数开销。这正是文档解题思路中所说的简单题按照题目描述模拟即可注意控制好 ptr 的位置。四、Go 源码实现详解仓库中该题的完整实现位于 1656. Design an Ordered Stream.go与文档中的代码完全一致package leetcode type OrderedStream struct { ptr int stream []string } func Constructor(n int) OrderedStream { ptr, stream : 1, make([]string, n1) return OrderedStream{ptr: ptr, stream: stream} } func (this *OrderedStream) Insert(id int, value string) []string { this.stream[id] value res : []string{} if this.ptr id || this.stream[this.ptr] ! { res append(res, this.stream[this.ptr]) for i : id 1; i len(this.stream); i { if this.stream[i] ! { res append(res, this.stream[i]) } else { this.ptr i return res } } } if len(res) 0 { return res } return []string{} }4.1 数据结构选型type OrderedStream struct { ptr int // 当前指针指向下一个期望返回的 ID stream []string // 下标即 IDstream[id] 存储 value }stream切片下标直接对应 IDmake([]string, n1)分配n1个元素下标 0 闲置不用使得stream[id]可以直接使用1..n的 ID 而不必做下标偏移同时stream[0]永远是空字符串不会干扰逻辑。ptr指针记录下一个应该输出的位置初始为1。空字符串在这里兼作该位置尚未填充的哨兵值因为题目保证 value 全部由小写字母构成不会与空串冲突。4.2 构造器初始化指针与容器func Constructor(n int) OrderedStream { ptr, stream : 1, make([]string, n1) return OrderedStream{ptr: ptr, stream: stream} }构造器完成两件事把ptr初始化为1对应题目要求的将当前指针 ptr 设为 1并分配容量为n1的切片。这里n1而非n是一个值得注意的实现细节——它让 ID 与下标完全对齐牺牲一个元素的空间换取代码的简洁与直白。4.3 Insert写入 条件触发 连续收集Insert的逻辑分三步第一步写入数据this.stream[id] valueO(1) 时间把(id, value)存入对应下标。此时该位置由空串变为非空。第二步判断是否触发返回if this.ptr id || this.stream[this.ptr] ! {触发条件有两个满足其一即可this.ptr id刚插入的数据恰好补上了指针所指的缺口当前连续段必然完整需要从ptr开始输出this.stream[this.ptr] ! 指针所指位置之前就已经有值例如上一步推进指针时该位置恰好被更早的数据填满同样满足输出条件。第三步从 ptr 开始连续收集并推进指针res append(res, this.stream[this.ptr]) for i : id 1; i len(this.stream); i { if this.stream[i] ! { res append(res, this.stream[i]) } else { this.ptr i return res } }先把ptr位置的值放入结果然后从id1开始向后扫描遇到非空位置就追加进结果遇到第一个空位置就把ptr推进到该空位并立即返回。注意这里的扫描起点是id1而不是ptr1——因为在触发条件下id与ptr的关系满足id ptr从id1开始扫描是安全且更高效的id之前的位置要么已被收集、要么必然连续就绪。收尾保护如果循环耗尽仍未遇到空位例如恰好插满整个流i遍历到len(this.stream)结束函数会落到最后的保护分支if len(res) 0 { return res } return []string{}若res非空说明确实有连续段被收集直接返回若res为空未进入触发分支返回空切片[]string{}。4.4 为什么返回空列表必须用[]string{}而不是nil函数签名要求返回String[]Go 中即[]string。题目示例期望输出[]。在 Go 中nil切片与空切片在fmt打印、JSON 序列化等场景下表现略有差异仓库实现特意在末尾统一返回[]string{}保证返回的一定是非 nil 的空切片与测试期望保持一致体现了严谨的工程习惯。五、复杂度分析维度结论说明时间复杂度单次 insert最坏 O(n)写入 O(1)连续收集阶段最坏向后扫描 n 个位置时间复杂度整体最坏 O(n²)恰好 n 次 insert每次最坏扫描 O(n)空间复杂度O(n)切片存储 n 个 value加上常量级指针从源码结构看该实现属于典型的空间换时间思路用 O(n) 的数组换取写入的 O(1) 随机访问再用指针避免重复扫描已输出的区间。对于n ≤ 1000的约束即使最坏情况 O(n²) 也只有 10⁶ 量级操作运行毫无压力。六、测试验证用仓库用例复现官方示例仓库为该题提供了配套测试文件 1656. Design an Ordered Stream_test.go完整复现了官方示例的执行序列package leetcode import ( fmt testing ) func Test_Problem1656(t *testing.T) { obj : Constructor(5) fmt.Printf(obj %v\n, obj) param1 : obj.Insert(3, ccccc) fmt.Printf(param_1 %v obj %v\n, param1, obj) param1 obj.Insert(1, aaaaa) fmt.Printf(param_1 %v obj %v\n, param1, obj) param1 obj.Insert(2, bbbbb) fmt.Printf(param_1 %v obj %v\n, param1, obj) param1 obj.Insert(5, eeeee) fmt.Printf(param_1 %v obj %v\n, param1, obj) param1 obj.Insert(4, ddddd) fmt.Printf(param_1 %v obj %v\n, param1, obj) }测试以Constructor(5)构造容量为 5 的流随后按(3,ccccc) → (1,aaaaa) → (2,bbbbb) → (5,eeeee) → (4,ddddd)的顺序依次插入期望输出依次为[]、[aaaaa]、[bbbbb,ccccc]、[]、[ddddd,eeeee]与第一节的逐步拆解完全吻合。测试中还通过fmt.Printf(obj %v\n, obj)打印每次插入后的完整对象状态可以直观看到ptr的推进过程插入(3,ccccc)后ptr仍为1位置 3 已填充插入(1,aaaaa)后ptr推进到2插入(2,bbbbb)后ptr推进到4插入(5,eeeee)后ptr保持4插入(4,ddddd)后ptr越过位置 5 到达末尾。如果要在本地验证可先确认 Go 环境已安装然后在仓库根目录执行该题的定向测试go test -v -run Test_Problem1656 ./leetcode/1656.Design-an-Ordered-Stream/该仓库使用统一的测试与覆盖率机制根目录的 gotest.sh 展示了仓库级测试命令的写法go test -covermodeatomic -coverprofilecoverage.txt ./leetcode/...对本函数而言由于代码路径覆盖了未触发返回与触发返回两类分支配合go test -cover可以验证分支覆盖率。仓库项目描述中声明的 100% test coverage 也要求每个题解都配有此类可复现的测试用例。七、关键边界条件盘点结合实现细节以下边界情况需要特别留意插入缺口id ptr如示例第一步insert(3, ccccc)ptr不移动返回空列表。此时必须保证不误输出。恰好补上缺口id ptr如insert(1, aaaaa)触发收集返回从ptr起的连续段。缺口早已补上stream[ptr] ! 如insert(2, bbbbb)时位置 3 早已有值触发后一路收集到位置 4 的空位。尾部连续填充如最后一步insert(4, ddddd)连续收集到容量尽头for 循环自然结束依赖收尾保护分支返回结果。下标 0 永不使用make([]string, n1)保证stream[id]在id ∈ [1, n]时永远不越界。八、延伸思考本题背后的通用设计模式虽然 1656 题被文档定位为简单题但其核心思想在工程中相当常见——用游标指针 就绪标记维护按序消费语义有序消息消费消息队列中乱序到达的分片如 TCP 的乱序段、视频的分段下载可借鉴指针指向下一个期望序号就绪则连续输出缺口则等待的模型流式数据处理聚合多个乱序来源的数据时用指针记录已连续处理到的位置避免重复或遗漏与优先队列方案的对比本题若改用最小堆实现每次 insert O(log n)但需要额外处理堆顶 ID 与 ptr 不一致时不出队的逻辑代码更复杂而题目给的n ≤ 1000约束下数组 指针的 O(n) 扫描方案更简洁直观这也体现了根据数据规模选择合适数据结构的工程判断力。仓库源码从解题思路到最终实现都严格遵循 LeetCode-Go 主 README 所声明的 Google Go 风格指南结构清晰、命名直白适合作为指针模拟类设计题的入门范本深入学习。小结本篇围绕 LeetCode-Go 仓库的 1656 题文档 展开完整覆盖了题目原文、题目大意、官方示例的逐步拆解、约束条件分析、源码级实现解读、复杂度分析以及仓库测试用例的验证方法。核心要点可归纳为三句话数据结构长度n1的字符串切片下标即 ID空串即未就绪哨兵核心指针ptr初始为 1只从缺口开始输出连续就绪段保证所有 chunk 拼接后天然有序边界控制区分插入缺口补上缺口尾部填满三类场景空返回统一使用[]string{}。掌握这道题你就掌握了乱序输入 按序输出这类设计题的通法可进一步迁移到消息队列排序消费、流式数据聚合等真实场景。【免费下载链接】LeetCode-Go✅ Solutions to LeetCode by Go, 100% test coverage, runtime beats 100% | LeetCode 题解项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表