LeetCode 1656 - Design an Ordered Stream
This problem asks us to simulate a data stream where each element arrives with a unique ID in arbitrary order. Each element consists of an integer idKey (between 1 and n) and a string value.
Difficulty: 🟢 Easy
Topics: Array, Hash Table, Design, Data Stream
Solution
Problem Understanding
This problem asks us to simulate a data stream where each element arrives with a unique ID in arbitrary order. Each element consists of an integer idKey (between 1 and n) and a string value. The goal is to design a stream that, after every insertion, returns the largest contiguous chunk of values that can be output in order starting from the last output point. Over time, all values should be returned in increasing order of idKey.
The input consists of a sequence of insertions. After each insertion, the output is a list of strings representing the next chunk of ordered values. If the inserted idKey does not continue the current sequence, the output is an empty list.
The constraints clarify that n is relatively small (up to 1000), each value has length 5, and IDs are unique. Every call to insert is guaranteed to have a unique ID and there will be exactly n insertions. This guarantees that eventually the stream will contain all values, so we do not need to handle missing or duplicate IDs.
Edge cases include inserting the first or last IDs first, inserting out-of-order sequences, and receiving IDs that leave gaps before the next chunk can be returned. A naive implementation that sorts the entire stream on every insertion would be unnecessarily slow.
Approaches
The brute-force approach would be to store all inserted (idKey, value) pairs in a map or array and, on every insert, sort all keys and return the next contiguous chunk starting from the last output ID. This works because sorting guarantees order, but sorting on every insertion is inefficient.
The optimal approach leverages the fact that IDs are integers in [1, n]. We can pre-allocate an array of size n + 1 (1-based indexing) to store the values directly at their corresponding indices. We also maintain a pointer ptr to track the smallest ID that has not yet been output. After inserting a value, we check if ptr points to a non-null value. If so, we collect all contiguous non-null values starting at ptr into a chunk and advance ptr accordingly. This approach avoids sorting and uses direct array access for constant-time insertion and sequential collection.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n log n) per insert | O(n) | Stores all values, sorts keys on each insertion |
| Optimal | O(1) per insert amortized | O(n) | Array-based storage with pointer tracking contiguous output |
Algorithm Walkthrough
-
Initialize an array
streamof sizen + 1withNone(or empty string in Go) to hold values at indices corresponding to IDs. -
Initialize a pointer
ptr = 1to track the next ID to output. -
For each
insert(idKey, value)call: -
Store
valueatstream[idKey]. -
Initialize an empty list
chunk. -
While
ptr <= nandstream[ptr]is notNone: -
Append
stream[ptr]tochunk. -
Increment
ptr. -
Return
chunk.
Why it works: The pointer ptr ensures we always start collecting output from the smallest unreturned ID. Storing values in an array indexed by idKey ensures O(1) access, and the while-loop collects contiguous values in order. This guarantees that every returned chunk is maximal and contiguous in ID order.
Python Solution
from typing import List
class OrderedStream:
def __init__(self, n: int):
self.stream = [None] * (n + 1) # 1-based indexing
self.ptr = 1
def insert(self, idKey: int, value: str) -> List[str]:
self.stream[idKey] = value
chunk = []
while self.ptr < len(self.stream) and self.stream[self.ptr] is not None:
chunk.append(self.stream[self.ptr])
self.ptr += 1
return chunk
Explanation: We pre-allocate an array stream with size n + 1 to store values directly at idKey. The ptr points to the smallest ID that hasn't been output yet. On each insertion, if ptr has a value, we collect all contiguous values starting at ptr. This implements the largest possible chunk logic efficiently.
Go Solution
type OrderedStream struct {
stream []string
ptr int
}
func Constructor(n int) OrderedStream {
return OrderedStream{
stream: make([]string, n+1), // 1-based indexing
ptr: 1,
}
}
func (this *OrderedStream) Insert(idKey int, value string) []string {
this.stream[idKey] = value
chunk := []string{}
for this.ptr < len(this.stream) && this.stream[this.ptr] != "" {
chunk = append(chunk, this.stream[this.ptr])
this.ptr++
}
return chunk
}
/**
* Your OrderedStream object will be instantiated and called as such:
* obj := Constructor(n);
* param_1 := obj.Insert(idKey,value);
*/
Go-specific notes: We use a slice of strings initialized with empty strings. Checking for "" replaces Python's None. Slice append handles dynamic chunk building.
Worked Examples
Consider the example from the problem statement:
| Operation | Stream State (index:value) | ptr | Returned Chunk |
|---|---|---|---|
| insert(3, "ccccc") | [None, None, None, "ccccc", None, None] | 1 | [] |
| insert(1, "aaaaa") | [None, "aaaaa", None, "ccccc", None, None] | 1→2 | ["aaaaa"] |
| insert(2, "bbbbb") | [None, "aaaaa", "bbbbb", "ccccc", None, None] | 2→4 | ["bbbbb", "ccccc"] |
| insert(5, "eeeee") | [None, "aaaaa", "bbbbb", "ccccc", None, "eeeee"] | 4 | [] |
| insert(4, "ddddd") | [None, "aaaaa", "bbbbb", "ccccc", "ddddd", "eeeee"] | 4→6 | ["ddddd", "eeeee"] |
Concatenating chunks: [] + ["aaaaa"] + ["bbbbb","ccccc"] + [] + ["ddddd","eeeee"] → ["aaaaa","bbbbb","ccccc","ddddd","eeeee"].
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(1) amortized per insert | Each element is visited once by ptr, total O(n) across n inserts |
| Space | O(n) | Array of size n+1 to store all values |
Because ptr only moves forward, the total time over all inserts is linear, making it O(1) amortized per insertion.
Test Cases
# Example from problem
os = OrderedStream(5)
assert os.insert(3, "ccccc") == [] # first out-of-order insert
assert os.insert(1, "aaaaa") == ["aaaaa"]
assert os.insert(2, "bbbbb") == ["bbbbb", "ccccc"]
assert os.insert(5, "eeeee") == []
assert os.insert(4, "ddddd") == ["ddddd", "eeeee"]
# Edge: inserting in perfect order
os2 = OrderedStream(3)
assert os2.insert(1, "aaa") == ["aaa"]
assert os2.insert(2, "bbb") == ["bbb"]
assert os2.insert(3, "ccc") == ["ccc"]
# Edge: inserting in reverse order
os3 = OrderedStream(3)
assert os3.insert(3, "ccc") == []
assert os3.insert(2, "bbb") == []
assert os3.insert(1, "aaa") == ["aaa", "bbb", "ccc"]
# Edge: single element
os4 = OrderedStream(1)
assert os4.insert(1, "solo") == ["solo"]
| Test | Why |
|---|---|
| Example | Verifies problem statement logic |
| Perfect order | Checks normal sequential insertion |
| Reverse order | Ensures algorithm waits for contiguous chunk |
| Single element | Minimal boundary case |
Edge Cases
Edge Case 1: Inserting IDs in reverse order. This could cause a naive implementation that assumes immediate output to fail. Our implementation handles it by returning empty chunks until the sequence can continue from ptr.
Edge Case 2: First or last ID is inserted first. If idKey = n arrives first, no values are output until preceding IDs arrive. Using ptr ensures we only output contiguous chunks starting from the first missing ID.
Edge Case 3: Stream with n = 1. Minimal input size can trigger off-by-one errors. Our 1-based array and pointer handle this naturally, producing a single output.
Edge Case 4: All inserts arrive in non-contiguous random order. The array allows O(1) insertions and chunk formation, ensuring performance does not degrade with random input.
This solution is robust for all inputs within the constraints.