Longest Uploaded Prefix
MedPrompt
You are given a stream of n videos, each represented by a distinct number from 1 to n that you need to "upload" to a server. You need to implement a data structure that calculates the length of the longest uploaded prefix at various points in the upload process.
We consider i to be an uploaded prefix if all videos in the range 1 to i (inclusive) have been uploaded to the server. The longest uploaded prefix is the maximum value of i that satisfies this definition.
Implement the LUPrefix class:
LUPrefix(int n)Initializes the object for a stream ofnvideos.void upload(int video)Uploadsvideoto the server.int longest()Returns the length of the longest uploaded prefix defined above.
Example 1:
Input
["LUPrefix", "upload", "longest", "upload", "longest", "upload", "longest"]
[[4], [3], [], [1], [], [2], []]
Output
[null, null, 0, null, 1, null, 3]
Explanation
LUPrefix server = new LUPrefix(4); // Initialize a stream of 4 videos.
server.upload(3); // Upload video 3.
server.longest(); // Since video 1 has not been uploaded yet, there is no prefix.
// So, we return 0.
server.upload(1); // Upload video 1.
server.longest(); // The prefix [1] is the longest uploaded prefix, so we return 1.
server.upload(2); // Upload video 2.
server.longest(); // The prefix [1,2,3] is the longest uploaded prefix, so we return 3.
Constraints:
1 <= n <= 1051 <= video <= n- All values of
videoare distinct. - At most
2 * 105calls in total will be made touploadandlongest. - At least one call will be made to
longest.
Approaches
3 approaches with complexity analysis and trade-offs.
This approach uses a boolean array to keep track of the uploaded videos. The upload operation is a simple O(1) update to this array. The longest operation finds the prefix length by iterating from the last known prefix length and checking for consecutive uploaded videos.
Algorithm
- Initialize a boolean array
uploadedof sizen+1tofalseand an integerprefixto0. - For
upload(video): Setuploaded[video]totrue. - For
longest():- Use a
whileloop to check ifprefixcan be extended. - The condition is
prefix < nanduploaded[prefix + 1] == true. - If the condition is met, increment
prefix. - Repeat until the condition is false.
- Return the final
prefixvalue.
- Use a
Walkthrough
We initialize a boolean array, say uploaded, of size n + 1. uploaded[i] will be true if video i has been uploaded, and false otherwise. We also maintain a variable, prefix, initialized to 0, which stores the length of the longest uploaded prefix found so far. This helps to avoid re-scanning from 1 every time.
The LUPrefix(n) constructor initializes this uploaded array and the prefix variable.
The upload(video) method simply marks the corresponding video as uploaded by setting uploaded[video] = true.
The longest() method contains the core logic. It enters a loop that starts checking from prefix + 1. As long as the next consecutive video (uploaded[prefix + 1]) is found to be uploaded, it increments prefix. The loop stops when it finds a video that has not been uploaded yet. Finally, it returns the current value of prefix.
class LUPrefix { private boolean[] uploaded; private int n; private int prefix; public LUPrefix(int n) { this.n = n; this.uploaded = new boolean[n + 1]; this.prefix = 0; } public void upload(int video) { uploaded[video] = true; } public int longest() { while (prefix < n && uploaded[prefix + 1]) { prefix++; } return prefix; }}Complexity
Time
Constructor: `O(n)` to initialize the boolean array. `upload(video)`: `O(1)` for a single array write operation. `longest()`: `O(k)` in the worst case for a single call, where `k` is the number of newly added consecutive videos that extend the prefix. In the absolute worst case, this can be `O(n)`. However, the total work done by the `while` loop across all calls to `longest()` is `O(n)`, making the amortized time complexity `O(1)`.
Space
`O(n)` to store the `uploaded` status for all `n` videos.
Trade-offs
Pros
Simple to understand and implement.
uploadoperation is extremely fast (O(1)).
Cons
The
longest()operation can be slow in the worst-case for a single call, potentially takingO(n)time.
This approach is a variation of the linear scan method. Instead of calculating the prefix length on-demand in longest(), we proactively update it during each upload operation. This makes the longest() call a simple O(1) lookup.
Algorithm
- Initialize a boolean array
uploadedof sizen+2tofalseand an integerprefixto0. - For
upload(video):- Set
uploaded[video]totrue. - Use a
whileloop to check ifprefixcan be extended:while (uploaded[prefix + 1]). - If the condition is met, increment
prefix. - Repeat until the condition is false.
- Set
- For
longest(): Return the currentprefixvalue.
Walkthrough
Similar to the previous approach, we use a boolean array uploaded and an integer prefix to store the current longest prefix length.
The LUPrefix(n) constructor initializes these structures.
The upload(video) method first marks the video as uploaded. Then, it checks if this new video helps extend the current prefix. It enters a while loop, identical to the one in the previous approach's longest() method, to increment prefix as long as consecutive videos are present.
The longest() method now becomes trivial. It simply returns the pre-calculated prefix value. This effectively shifts the computational work from longest() to upload().
class LUPrefix { private boolean[] uploaded; private int n; private int prefix; public LUPrefix(int n) { this.n = n; // Use n+2 to avoid bounds check for uploaded[prefix+1] when prefix=n this.uploaded = new boolean[n + 2]; this.prefix = 0; } public void upload(int video) { uploaded[video] = true; while (uploaded[prefix + 1]) { prefix++; } } public int longest() { return prefix; }}Complexity
Time
Constructor: `O(n)` to initialize the boolean array. `upload(video)`: `O(k)` in the worst case, where `k` is the number of newly added consecutive videos. This can be `O(n)` in the worst case for a single call (e.g., uploading videos `n, n-1, ..., 2` and then `1`). The amortized time complexity is `O(1)` as the `while` loop's body executes at most `n` times in total across all calls. `longest()`: `O(1)` as it just returns a variable.
Space
`O(n)` for the boolean array.
Trade-offs
Pros
longest()operation is guaranteed to beO(1), which is ideal for query-heavy scenarios.Still relatively simple to implement.
Cons
The
upload()operation can be slow in the worst-case for a single call, takingO(n)time.
This is the most efficient approach in terms of worst-case time complexity per operation. It uses a Disjoint Set Union (DSU) data structure, also known as Union-Find, to group consecutive uploaded videos. The length of the longest prefix is then the size of the set that contains video 1.
Algorithm
- Initialize a DSU structure for
n+1elements, withparent[i] = iandsize[i] = 1. - Initialize a boolean array
uploadedof sizen+2tofalse. - For
upload(video):- Set
uploaded[video]totrue. - If
video-1is uploaded,union(video, video-1). - If
video+1is uploaded,union(video, video+1).
- Set
- For
longest():- If
uploaded[1]isfalse, return 0. - Otherwise, return the size of the set containing 1, which is
size[find(1)].
- If
Walkthrough
We use three main data structures: a boolean array uploaded to track uploaded videos, and two arrays for the DSU, parent and sz (size). The DSU will manage n+1 elements (for videos 1 to n).
The LUPrefix(n) constructor initializes these arrays. Each video starts in its own set.
The upload(video) method first marks the video as uploaded. Then, it checks its neighbors (video - 1 and video + 1). If a neighbor has also been uploaded, it performs a union operation to merge the sets of the current video and its neighbor. This effectively connects contiguous blocks of uploaded videos.
The longest() method checks if video 1 has been uploaded. If not, the prefix is 0. If it has, the length of the longest prefix is simply the size of the set containing video 1. This can be found by sz[find(1)], where find(1) gives the representative of the set containing 1.
Using path compression and union by size/rank optimizations, the find and union operations are nearly constant time.
class LUPrefix { private int[] parent; private int[] sz; // size of component private boolean[] uploaded; private int n; public LUPrefix(int n) { this.n = n; this.parent = new int[n + 1]; this.sz = new int[n + 1]; this.uploaded = new boolean[n + 2]; // Use n+2 for easier neighbor checks for (int i = 0; i <= n; i++) { parent[i] = i; sz[i] = 1; } } private int find(int i) { if (parent[i] == i) { return i; } return parent[i] = find(parent[i]); } private void union(int i, int j) { int rootI = find(i); int rootJ = find(j); if (rootI != rootJ) { if (sz[rootI] < sz[rootJ]) { int temp = rootI; rootI = rootJ; rootJ = temp; } parent[rootJ] = rootI; sz[rootI] += sz[rootJ]; } } public void upload(int video) { uploaded[video] = true; // Union with left neighbor if it exists and is uploaded if (video > 1 && uploaded[video - 1]) { union(video, video - 1); } // Union with right neighbor if it exists and is uploaded if (video < n && uploaded[video + 1]) { union(video, video + 1); } } public int longest() { if (!uploaded[1]) { return 0; } // The size of the component containing 1 is the longest prefix return sz[find(1)]; }}Complexity
Time
Constructor: `O(n)` to initialize the DSU arrays and `uploaded` array. `upload(video)`: `O(α(n))`, where `α(n)` is the very slow-growing inverse Ackermann function. This is due to the two potential `union` operations. `longest()`: `O(α(n))` due to the `find` operation.
Space
`O(n)` for the `parent`, `sz`, and `uploaded` arrays.
Trade-offs
Pros
Provides excellent and consistent performance for both
uploadandlongestoperations.Worst-case time complexity for both operations is nearly constant.
Cons
More complex to implement compared to the linear scan approaches.
Slightly higher constant factor overhead due to the DSU logic.
Solutions
Solution
class LUPrefix { private int r ; private Set < Integer > s = new HashSet <>(); public LUPrefix ( int n ) { } public void upload ( int video ) { s . add ( video ); while ( s . contains ( r + 1 )) { ++ r ; } } public int longest () { return r ; } } /** * Your LUPrefix object will be instantiated and called as such: * LUPrefix obj = new LUPrefix(n); * obj.upload(video); * int param_2 = obj.longest(); */class LUPrefix { public: LUPrefix ( int n ) { } void upload ( int video ) { s . insert ( video ); while ( s . count ( r + 1 )) { ++ r ; } } int longest () { return r ; } private: int r = 0 ; unordered_set < int > s ; }; /** * Your LUPrefix object will be instantiated and called as such: * LUPrefix* obj = new LUPrefix(n); * obj->upload(video); * int param_2 = obj->longest(); */class LUPrefix : def __init__ ( self , n : int ): self . r = 0 self . s = set () def upload ( self , video : int ) -> None : self . s . add ( video ) while self . r + 1 in self . s : self . r += 1 def longest ( self ) -> int : return self . r # Your LUPrefix object will be instantiated and called as such: # obj = LUPrefix(n) # obj.upload(video) # param_2 = obj.longest()Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.