22. Challenge Yourself#
These problems sit at the intersection of multiple DSA patterns. They are the capstone of this guide: you must design a data structure (or combine several) so that every operation meets a strict time bound. Expect hash maps, linked lists, heaps, binary search, and interval logic in the same solution.
Use this section after you have worked through the individual pattern chapters. For each problem, identify which patterns it combines before reading the solution.
Design Data Structure#
| Problem | Primary patterns combined |
|---|---|
| LRU Cache (146) | Hash Map · Linked List |
| LFU Cache (460) | Hash Map · Linked List · Greedy (min-freq tracking) |
| Insert Delete GetRandom O(1) (380) | Hash Map · Array |
| Time Based Key-Value Store (981) | Hash Map · Binary Search |
| Snapshot Array (1146) | Hash Map · Array (lazy versioning) |
| Design Twitter (355) | Hash Map · Heap · K-way Merge |
| Design Underground System (1396) | Hash Map · Prefix Sum (running aggregates) |
| Range Module (715) | Overlapping Intervals · Binary Search (sorted structure) |
| Find Median from Data Stream (295) | Heaps (two-heap pattern) |
| Design Browser History (1472) | Linked List · Stack-like navigation |
Problem 1: LRU Cache (Leetcode:146)#
Problem Statement
Design a data structure that follows the constraints of a Least Recently Used (LRU) cache.
Implement the LRUCache class:
LRUCache(int capacity)Initialize the LRU cache with positive sizecapacity.int get(int key)Return the value of thekeyif the key exists, otherwise return-1.void put(int key, int value)Update the value of thekeyif the key exists. Otherwise, add thekey-valuepair to the cache. If the number of keys exceeds thecapacityfrom this operation, evict the least recently used key.
The functions get and put must each run in O(1) average time complexity.
Example 1:
Input: ["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
Output: [null, null, null, 1, null, -1, null, -1, 3, 4]
Constraints:
1 <= capacity <= 30000 <= key <= 1040 <= value <= 105- At most
2 * 105calls will be made togetandput.
Patterns: Hash Map · Linked List
Code and Explanation
- A hash map gives O(1) lookup by key; a doubly linked list orders nodes by recency.
- Dummy head/tail nodes avoid null checks. MRU sits near
head, LRU neartail. - On
getorputhit: remove the node and splice it to the front (mark most recent). - On
putmiss when full: evict the node beforetail(LRU), then insert the new entry at the front.
Time: O(1) per operation · Space: O(capacity)
OrderedDictmaintains insertion order and supportsmove_to_endin O(1).popitem(last=False)removes the oldest (LRU) entry.- Ideal for interviews in Python; Approach 1 is what you should implement in Java/C++.
Time: O(1) per operation · Space: O(capacity)
Problem 2: LFU Cache (Leetcode:460)#
Problem Statement
Design and implement a data structure for a Least Frequently Used (LFU) cache.
Implement the LFUCache class:
LFUCache(int capacity)Initializes the object with thecapacityof the data structure.int get(int key)Gets the value of thekeyif the key exists in the cache. Otherwise, returns-1.void put(int key, int value)Update the value of thekeyif present, or insert thekey-valuepair. When the cache reaches itscapacity, it should invalidate and remove the least frequently used key. If there is a tie, remove the least recently used key among them.
The functions get and put must run in O(1) average time complexity.
Example 1:
Input: ["LFUCache", "put", "put", "get", "put", "get", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [3], [4, 4], [1], [3], [4]]
Output: [null, null, null, 1, null, -1, 3, null, -1, 3, 4]
Constraints:
1 <= capacity <= 1040 <= key <= 1050 <= value <= 109- At most
105calls will be made togetandput.
Patterns: Hash Map · Linked List · Greedy (track global min frequency)
Code and Explanation
- Track each key's node in a hash map; group nodes by frequency in doubly linked lists (one list per frequency).
- Maintain
min_freq— the global minimum frequency currently in the cache. - On access: remove node from its old freq list; if that list empties and it was
min_freq, incrementmin_freq; bump freq and push to front of the new freq list. - On insert when full: evict the back of the
min_freqlist (least recently used among tied frequencies).
Time: O(1) per operation · Space: O(capacity)
key_to_node: {1 → Node(val=1, freq=2), 2 → Node(val=2, freq=1), ...}
freq=1: tail ← [2] ← head
freq=2: tail ← [1] ← head
min_freq = 1
- Think of LRU per frequency level: each frequency bucket is a mini-LRU cache.
- Eviction always targets
freq_to_list[min_freq]— the least frequent, and within that bucket the LRU end. - This is strictly harder than LRU Cache because you juggle two ordering criteria with O(1) updates.
Time: O(1) per operation · Space: O(capacity)
Problem 3: Insert Delete GetRandom O(1) (Leetcode:380)#
Problem Statement
Implement the RandomizedSet class:
RandomizedSet()Initializes theRandomizedSetobject.bool insert(int val)Inserts an itemvalinto the set if not present. Returnstrueif the item was not present,falseotherwise.bool remove(int val)Removes an itemvalfrom the set if present. Returnstrueif the item was present,falseotherwise.int getRandom()Returns a random element from the current set. Each element must have the same probability of being returned.
You must implement the functions of the class such that each function works in average O(1) time complexity.
Example 1:
Input: ["RandomizedSet", "insert", "remove", "insert", "getRandom", "remove", "insert", "getRandom"]
[[], [1], [2], [2], [], [1], [2], []]
Output: [null, true, false, true, 2, true, false, 2]
Constraints:
-231 <= val <= 231 - 1- At most
2 * 105calls will be made toinsert,remove, andgetRandom.- There will be at least one element in the structure when
getRandomis called.
Patterns: Hash Map · Array
Code and Explanation
- Array enables O(1) random index pick; hash map maps value → index for O(1) lookup.
- Delete is the trick: swap the target with the last element, update the moved element's index, then pop — O(1) without shifting.
- Follow-up 380 + duplicates (381) uses
dict[val, set of indices]and the same swap logic per index.
Time: O(1) average per operation · Space: O(n)
| Structure | insert | remove | getRandom |
|---|---|---|---|
set |
O(1) | O(1) | O(n) — no random index |
list |
O(1) append | O(n) — search + shift | O(1) |
list + dict |
O(1) | O(1) swap | O(1) |
Explanation:
- Each operation alone is easy; the challenge is satisfying all three in O(1) simultaneously.
- The swap-with-last invariant keeps the array dense with no holes.
Time: O(1) average · Space: O(n)
Problem 4: Time Based Key-Value Store (Leetcode:981)#
Problem Statement
Design a time-based key-value data structure that can store multiple values for the same key at different timestamps and retrieve the key's value at a certain timestamp.
Implement the TimeMap class:
TimeMap()Initializes the object.void set(String key, String value, int timestamp)Stores the keykeywith the valuevalueat the given timetimestamp.String get(String key, int timestamp)Returns a value such thatsetwas called previously, withtimestamp_prev <= timestamp. If there are multiple such values, it returns the value associated with the largesttimestamp_prev. If there are no values, it returns"".
Example 1:
Input: ["TimeMap", "set", "get", "get", "set", "get", "get"]
[[], ["foo", "bar", 1], ["foo", 1], ["foo", 3], ["foo", "bar2", 4], ["foo", 4], ["foo", 5]]
Output: [null, null, "bar", "bar", null, "bar2", "bar2"]
Constraints:
1 <= key.length, value.length <= 100keyandvalueconsist of lowercase English letters and digits.1 <= timestamp <= 107- All timestamps of
setare strictly increasing.- At most
2 * 105calls will be made tosetandget.
Patterns: Hash Map · Binary Search
Code and Explanation
- Hash map maps each key to a list of
(timestamp, value)pairs sorted by timestamp (guaranteed by strictly increasingsetcalls). - Binary search (
bisect_right) finds the rightmost timestamp ≤ query — classic "floor" search on a sorted array. - The sentinel
chr(127)in the tuple ensures we compare timestamps first, then break ties by value (irrelevant when timestamps are unique).
Time: O(1) set · O(log n) get per key · Space: O(total entries)
- Standard lower-bound / floor binary search: shrink the right side when
entries[mid].timestamp > query. - Keep the best candidate in
answheneverentries[mid].timestamp <= timestamp. - Equivalent to
bisect; use this form whenbisectis unavailable.
Time: O(1) set · O(log n) get · Space: O(total entries)
Problem 5: Snapshot Array (Leetcode:1146)#
Problem Statement
Implement a SnapshotArray that supports the following interface:
SnapshotArray(int length)Initializes an array-like data structure with the given length. Initially, each element equals 0.void set(index, val)Sets the element at the givenindexto be equal toval.int snap()Takes a snapshot of the array and returns thesnap_id(incremental ID assigned to the snapshot starting from 0).int get(index, snap_id)Returns the value atindexat the time the snapshot withsnap_idwas taken.
Example 1:
Input: ["SnapshotArray", "set", "snap", "get", "set", "snap", "get"]
[[3], [0, 5], [], [0, 0], [0, 6], [], [0, 1]]
Output: [null, null, 0, 5, null, 0, 6]
Constraints:
1 <= length <= 5 * 1040 <= index < length0 <= val <= 1090 <= snap_id < snap()calls- At most
5 * 104calls will be made toset,snap, andget.
Patterns: Hash Map · Array (lazy versioning) · Binary Search
Code and Explanation
- Never copy the full array on
snap— only increment a counter. Each index stores a sorted list of(snap_id, value)changes. (0, 0)seeds every index with the initial zero at snapshot 0.get(index, snap_id)uses binary search to find the latest change withchange_snap ≤ snap_id.
Time: O(1) set · O(1) snap · O(log k) get (k = changes per index) · Space: O(length + total sets)
- Deep-copy the array on every
snap— O(n) per snapshot. - Correct and easy to explain, but fails when
snap()is called frequently on large arrays. - Approach 1 is the intended design: lazy versioning with per-cell history.
Time: O(n) snap · O(1) get · Space: O(n × num_snaps)
Problem 6: Design Twitter (Leetcode:355)#
Problem Statement
Design a simplified version of Twitter where users can post tweets, follow/unfollow another user, and see the 10 most recent tweet ids in the user's news feed.
Implement the Twitter class:
Twitter()Initializes your twitter object.void postTweet(int userId, int tweetId)Creates a new tweet with IDtweetIdby the useruserId. Each call will be made with a unique tweetId.List<Integer> getNewsFeed(int userId)Retrieves the 10 most recent tweet IDs in the user's feed. Each item must be posted by users who the user followed or by the user themself. Tweets must be ordered from most recent to least recent.void follow(int followerId, int followeeId)The user with IDfollowerIdfollows the user with IDfolloweeId.void unfollow(int followerId, int followeeId)The user with IDfollowerIdunfollowed the user with IDfolloweeId.
Example 1:
Input: ["Twitter", "postTweet", "getNewsFeed", "follow", "postTweet", "getNewsFeed", "unfollow", "getNewsFeed"]
[[], [1, 5], [1], [1, 2], [2, 6], [1], [1, 2], [1]]
Output: [null, null, [5], null, null, [6, 5], null, [5]]
Constraints:
1 <= userId, followerId, followeeId <= 5000 <= tweetId <= 104- All tweets have unique IDs.
- At most
3 * 104calls will be made topostTweet,getNewsFeed,follow, andunfollow.
Patterns: Hash Map · Heap · K-way Merge
Code and Explanation
- Store each user's tweets as a chronologically sorted list with a global timestamp counter.
getNewsFeedis a k-way merge: seed a min-heap (negate time for max-heap) with each followed user's latest tweet.- Pop the most recent, push that user's next-older tweet — same pattern as K-way Merge / merge k sorted lists. Stop after 10.
Time: O(1) post/follow/unfollow · O(F log F) feed (F ≤ 11 users, capped at 10 pops) · Space: O(total tweets + follows)
- Push each new tweet into every follower's precomputed feed on
postTweet. getNewsFeedbecomes O(1), butpostTweetcan be O(users × followers) — bad at scale.- Approach 1 (heap merge on read) is the standard interview answer.
Time: O(1) getNewsFeed · O(U) postTweet · Space: O(total stored feed entries)
Problem 7: Design Underground System (Leetcode:1396)#
Problem Statement
An underground railway system tracks customer travel times between different stations. Implement the UndergroundSystem class:
void checkIn(int id, string stationName, int t)A customer with ididchecks in at stationstationNameat timet.void checkOut(int id, string stationName, int t)A customer with ididchecks out from stationstationNameat timet.double getAverageTime(string startStation, string endStation)Returns the average time taken to travel fromstartStationtoendStationacross all previous trips.
Example 1:
Input: ["UndergroundSystem", "checkIn", "checkIn", "checkIn", "checkOut", "checkOut", "checkOut", "getAverageTime", "getAverageTime", "checkIn", "getAverageTime", "checkOut", "getAverageTime"]
[[], [45, "Leyton", 3], [32, "Paradise", 8], [27, "Leyton", 10], [45, "Waterloo", 15], [27, "Waterloo", 20], [32, "Cambridge", 22], ["Paradise", "Cambridge"], ["Leyton", "Waterloo"], [10, "Leyton", 24], ["Leyton", "Waterloo"], [10, "Waterloo", 38], ["Leyton", "Waterloo"]]
Output: [null, null, null, null, null, null, null, 14.0, 11.0, null, 11.0, null, 12.0]
Constraints:
1 <= id, t <= 106- All strings consist of uppercase and lowercase English letters and digits.
1 <= stationName.length <= 10- At most
2 * 104calls will be made tocheckIn,checkOut, andgetAverageTime.- Answers within
10-5of the actual value will be accepted.
Patterns: Hash Map · Prefix Sum (running total / count aggregates)
Code and Explanation
- Hash map 1 tracks in-progress trips:
id → (start_station, start_time). - Hash map 2 aggregates each directed route
(start, end) → [total_time, trip_count]— a running prefix sum style aggregate. - No need to store individual trip durations; update totals on checkout and divide on query.
Time: O(1) per operation · Space: O(active users + distinct routes)
- Append each trip duration to a list per route; average = sum / len on query.
getAverageTimebecomes O(k) in the number of trips for that route.- Approach 1 is preferred — O(1) query with incremental aggregation.
Time: O(1) checkIn/Out · O(k) getAverageTime · Space: O(total trips)
Problem 8: Range Module (Leetcode:715)#
Problem Statement
A range module tracks half-open intervals [left, right). Implement the RangeModule class:
void addRange(int left, int right)Adds[left, right)to the module.boolean queryRange(int left, int right)Returnstrueif[left, right)is fully covered.void removeRange(int left, int right)Removes[left, right)from the module.
Example 1:
Input: ["RangeModule", "addRange", "removeRange", "queryRange", "queryRange", "addRange", "queryRange", "queryRange", "removeRange", "queryRange"]
[[], [10, 20], [14, 16], [10, 14], [13, 15], [16, 22], [21, 22], [15, 17], [16, 17], [16, 17]]
Output: [null, null, null, true, false, null, true, false, null, true]
Constraints:
1 <= left < right <= 109- At most
104calls will be made toaddRange,queryRange, andremoveRange.
Patterns: Overlapping Intervals · Binary Search (sorted interval list)
Code and Explanation
- Maintain a sorted, non-overlapping list of half-open intervals — classic Overlapping Intervals merge logic.
addRange: skip intervals entirely left of the new range, merge overlapping/touching ones, append the expanded range, then copy the rest.queryRange: binary search for the interval containingleft; check if it (or the previous interval) covers throughright.removeRange: split any interval that partially overlaps the removal window.
Time: O(n) add/remove · O(log n) query · Space: O(n)
Explanation:
- In Java,
TreeMap<Integer, Integer>mapping start → end gives O(log n) add/remove/query via ceiling/floor keys. - In Python,
sortedcontainers.SortedListachieves the same; the manual list above is interview-portable without extra libraries. - Segment trees also work but are overkill for ≤ 10⁴ operations — interval merging is the intended pattern.
Time: O(log n) per operation with balanced tree · Space: O(n)
Problem 9: Find Median from Data Stream (Leetcode:295)#
Problem Statement
The median is the middle value in an ordered list. For a stream of integers, implement a data structure that supports:
MedianFinder()Initializes the object.void addNum(int num)Adds the integernumfrom the data stream to the data structure.double findMedian()Returns the median of all elements so far.
Example 1:
Input: ["MedianFinder", "addNum", "addNum", "findMedian", "addNum", "findMedian"]
[[], [1], [2], [], [3], []]
Output: [null, null, null, 1.5, null, 2.0]
Constraints:
-105 <= num <= 105- At most
5 * 104calls will be made toaddNumandfindMedian.- It is guaranteed that there will be at least one element before calling
findMedian.
Patterns: Heaps (two-heap / running median)
Code and Explanation
- Split numbers into two halves:
lo(max-heap, lower half) andhi(min-heap, upper half). - Invariant: every value in
lo≤ every value inhi, and|len(lo) - len(hi)| ≤ 1. - Push to
lo, balance by moving the max oflotohi, then rebalance sizes if needed. - Median is the top of the larger heap (odd count) or the average of both tops (even count). See Heaps — Two Heaps.
Time: O(log n) addNum · O(1) findMedian · Space: O(n)
- Keep a sorted list; insert with
bisect.insort— O(n) per add due to shifting. - Correct and easy to code under time pressure for small inputs.
- Two-heap approach is the intended O(log n) design.
Time: O(n) addNum · O(1) findMedian · Space: O(n)
Problem 10: Design Browser History (Leetcode:1472)#
Problem Statement
You have a browser of one tab where you start on the homepage and can visit another URL, go back steps times, or go forward steps times.
Implement the BrowserHistory class:
BrowserHistory(string homepage)Initializes with the homepage.void visit(string url)Visitsurlfrom the current page. All forward history is cleared.string back(int steps)Movestepsback in history. If you can only backxsteps, return the page afterxback.string forward(int steps)Movestepsforward. Same rule if fewer pages exist ahead.
Example 1:
Input: ["BrowserHistory", "visit", "visit", "visit", "back", "back", "forward", "visit", "forward", "back", "back"]
[["leetcode.com"], ["google.com"], ["facebook.com"], ["youtube.com"], [1], [1], [1], ["linkedin.com"], [2], [2], [7]]
Output: [null, null, null, null, "facebook.com", "google.com", "facebook.com", null, "linkedin.com", "google.com", "leetcode.com"]
Constraints:
1 <= homepage.length <= 201 <= url.length <= 201 <= steps <= 100- At most
5000calls.
Patterns: Linked List · Stack-like navigation (array + pointer)
Code and Explanation
- Model history as an array with a pointer
cur— same mental model as a linked list with forward/back links, but simpler in Python. visittruncates forward history (history[:cur+1]) then appends — like clearing the forward stack in a browser.back/forwardclamp the pointer to valid bounds and return the page atcur.
Time: O(1) back/forward · O(n) visit (truncate copy) · Space: O(total visits)
- True doubly linked list: each
visitcreates a new node and severs the old forward chain automatically (orphaned nodes are GC'd). - Walk
prev/nextpointers for back and forward — O(steps) per call. - Array + pointer (Approach 1) is usually faster in practice; linked list matches the problem's mental model.
Time: O(steps) back/forward · O(1) visit · Space: O(total visits)