1912. Design Movie Rental System
Problem Description
You need to design a movie rental system for a company with n
shops. Each shop can have at most one copy of any movie, and each movie copy has a specific rental price at that shop.
The system receives initial data as a 2D array entries
where each entries[i] = [shop_i, movie_i, price_i]
means shop shop_i
has a copy of movie movie_i
available for rent at price price_i
.
The system must support four operations:
-
Search(movie): Find the 5 cheapest shops that have an unrented copy of the specified movie. Results should be sorted by:
- Price (ascending)
- If prices are equal, by shop ID (ascending)
- Return fewer than 5 shops if that's all that's available
- Return empty list if no unrented copies exist
-
Rent(shop, movie): Mark a specific movie copy at a specific shop as rented (removing it from available inventory).
-
Drop(shop, movie): Return a previously rented movie to a specific shop (making it available again).
-
Report(): Find the 5 cheapest currently rented movies across all shops. Results should be returned as
[shop, movie]
pairs sorted by:- Price (ascending)
- If prices are equal, by shop ID (ascending)
- If shop IDs are also equal, by movie ID (ascending)
- Return fewer than 5 if that's all that's rented
- Return empty list if nothing is rented
The solution uses three main data structures:
unrented
: A dictionary mapping each movie to a sorted list of(price, shop)
tuples for available copiesshopAndMovieToPrice
: A dictionary storing the price for each(shop, movie)
combinationrented
: A sorted list of(price, shop, movie)
tuples for all currently rented movies
The SortedList
data structure automatically maintains sorted order during insertions and deletions, making it efficient to retrieve the cheapest options for both search and report operations.
Intuition
The key insight is that we need to efficiently maintain and query two separate groups of movies: those available for rent and those currently rented. Since we frequently need to find the "cheapest 5" items from both groups, we need data structures that keep items sorted.
Let's think about what operations we need to perform:
- When searching for a movie, we need the cheapest available copies of that specific movie
- When reporting, we need the cheapest rented movies across ALL movies
- When renting/dropping, we need to move items between the "available" and "rented" groups
This suggests we need:
- A way to group available movies by movie ID (since search is by movie ID)
- A way to track all rented movies together (since report looks across all movies)
- Quick access to prices for any
(shop, movie)
pair
For the available movies, we use a dictionary where each movie ID maps to its available copies. Since we always want the cheapest ones, we store these copies in a SortedList
as (price, shop)
tuples. Python naturally sorts tuples by comparing elements left-to-right, so (price, shop)
automatically gives us the ordering we want.
For rented movies, we use a single SortedList
containing (price, shop, movie)
tuples. Again, the tuple ordering naturally handles our tie-breaking rules: first by price, then by shop, then by movie.
The shopAndMovieToPrice
dictionary is crucial because when we rent or drop a movie, we only know the shop and movie IDs - but we need the price to properly add/remove items from our sorted lists. This dictionary gives us O(1) price lookups.
Using SortedList
(from the sortedcontainers library) is the key efficiency trick here. It maintains sorted order while allowing O(log n) insertions and deletions, making our rent/drop operations efficient. Getting the first 5 elements is O(1) since they're always at the front of the sorted structure.
Learn more about Heap (Priority Queue) patterns.
Solution Approach
The implementation uses three main data structures to efficiently handle all operations:
Data Structures
unrented
: Adefaultdict(SortedList)
that maps each movie ID to a sorted list of(price, shop)
tuples representing available copiesrented
: ASortedList
containing(price, shop, movie)
tuples for all currently rented moviesshopAndMovieToPrice
: A dictionary mapping(shop, movie)
pairs to their prices for quick lookups
Implementation Details
Initialization (__init__
):
def __init__(self, n: int, entries: List[List[int]]):
self.unrented = collections.defaultdict(SortedList)
self.shopAndMovieToPrice = {}
self.rented = SortedList()
for shop, movie, price in entries:
self.unrented[movie].add((price, shop))
self.shopAndMovieToPrice[(shop, movie)] = price
We iterate through all entries and:
- Add each movie copy to the
unrented
dictionary under its movie ID as a(price, shop)
tuple - Store the price in
shopAndMovieToPrice
for future reference - Initialize
rented
as empty since no movies are initially rented
Search Operation:
def search(self, movie: int) -> List[int]:
return [shop for _, shop in self.unrented[movie][:5]]
- Access the sorted list of available copies for the given movie
- Take the first 5 entries (or fewer if less than 5 exist)
- Extract just the shop IDs from the
(price, shop)
tuples - The
SortedList
ensures these are already in the correct order
Rent Operation:
def rent(self, shop: int, movie: int) -> None:
price = self.shopAndMovieToPrice[(shop, movie)]
self.unrented[movie].remove((price, shop))
self.rented.add((price, shop, movie))
- Look up the price using our price dictionary
- Remove the
(price, shop)
tuple from the unrented list for this movie - Add a
(price, shop, movie)
tuple to the rented list - Both operations maintain sorted order automatically
Drop Operation:
def drop(self, shop: int, movie: int) -> None:
price = self.shopAndMovieToPrice[(shop, movie)]
self.unrented[movie].add((price, shop))
self.rented.remove((price, shop, movie))
- Reverse of the rent operation
- Look up the price
- Add the movie back to the unrented list
- Remove it from the rented list
Report Operation:
def report(self) -> List[List[int]]:
return [[shop, movie] for _, shop, movie in self.rented[:5]]
- Take the first 5 entries from the sorted rented list
- Extract
[shop, movie]
pairs, discarding the price - The tuple structure
(price, shop, movie)
ensures correct sorting order
Time Complexity
- Initialization: O(m log m) where m is the number of entries
- Search: O(1) to access the list, O(5) = O(1) to extract results
- Rent/Drop: O(log k) where k is the number of copies of a specific movie or rented movies
- Report: O(1) to access first 5 elements
The SortedList
data structure is crucial for efficiency, providing O(log n) insertions/deletions while maintaining sorted order, making it perfect for this problem's requirements.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate how the solution works.
Initial Setup:
n = 3 shops (shops 0, 1, 2) entries = [[0, 1, 5], [0, 2, 3], [1, 1, 4], [1, 2, 6], [2, 1, 7]]
This means:
- Shop 0 has: Movie 1 at 3
- Shop 1 has: Movie 1 at 6
- Shop 2 has: Movie 1 at $7
After initialization, our data structures look like:
unrented = { 1: [(4, 1), (5, 0), (7, 2)] # Movie 1 sorted by price 2: [(3, 0), (6, 1)] # Movie 2 sorted by price } shopAndMovieToPrice = { (0, 1): 5, (0, 2): 3, (1, 1): 4, (1, 2): 6, (2, 1): 7 } rented = [] # Empty initially
Operation 1: Search(movie=1)
- Look at
unrented[1]
=[(4, 1), (5, 0), (7, 2)]
- Take first 5 (we only have 3):
[(4, 1), (5, 0), (7, 2)]
- Extract shop IDs:
[1, 0, 2]
- Returns:
[1, 0, 2]
(shops sorted by price: 5, $7)
Operation 2: Rent(shop=1, movie=1)
- Look up price:
shopAndMovieToPrice[(1, 1)]
= 4 - Remove
(4, 1)
fromunrented[1]
- Add
(4, 1, 1)
torented
Now:
unrented[1] = [(5, 0), (7, 2)] rented = [(4, 1, 1)]
Operation 3: Rent(shop=0, movie=2)
- Look up price:
shopAndMovieToPrice[(0, 2)]
= 3 - Remove
(3, 0)
fromunrented[2]
- Add
(3, 0, 2)
torented
Now:
unrented[2] = [(6, 1)] rented = [(3, 0, 2), (4, 1, 1)] # Automatically sorted by price
Operation 4: Report()
- Look at
rented
=[(3, 0, 2), (4, 1, 1)]
- Take first 5 (we only have 2):
[(3, 0, 2), (4, 1, 1)]
- Extract
[shop, movie]
pairs:[[0, 2], [1, 1]]
- Returns:
[[0, 2], [1, 1]]
(sorted by price: 4)
Operation 5: Drop(shop=0, movie=2)
- Look up price:
shopAndMovieToPrice[(0, 2)]
= 3 - Add
(3, 0)
back tounrented[2]
- Remove
(3, 0, 2)
fromrented
Now:
unrented[2] = [(3, 0), (6, 1)] # Automatically re-sorted rented = [(4, 1, 1)]
Operation 6: Search(movie=2)
- Look at
unrented[2]
=[(3, 0), (6, 1)]
- Take first 5 (we only have 2):
[(3, 0), (6, 1)]
- Extract shop IDs:
[0, 1]
- Returns:
[0, 1]
(shops sorted by price: 6)
This example demonstrates how:
- The
SortedList
structures automatically maintain sort order during insertions and removals - The tuple structure
(price, shop)
or(price, shop, movie)
provides natural sorting - The
shopAndMovieToPrice
dictionary enables quick price lookups during rent/drop operations - Search operations are fast because we can directly access the sorted list for a specific movie
- Report operations are fast because all rented movies are maintained in a single sorted structure
Solution Implementation
1from typing import List
2from collections import defaultdict
3from sortedcontainers import SortedList
4
5
6class MovieRentingSystem:
7 def __init__(self, n: int, entries: List[List[int]]):
8 """
9 Initialize the movie renting system.
10
11 Args:
12 n: Number of shops
13 entries: List of [shop, movie, price] entries
14 """
15 # Store unrented movies: movie_id -> sorted list of (price, shop_id)
16 self.unrented = defaultdict(SortedList)
17
18 # Store price lookup: (shop_id, movie_id) -> price
19 self.shop_and_movie_to_price = {}
20
21 # Store currently rented movies: sorted list of (price, shop_id, movie_id)
22 self.rented = SortedList()
23
24 # Process initial entries
25 for shop, movie, price in entries:
26 self.unrented[movie].add((price, shop))
27 self.shop_and_movie_to_price[(shop, movie)] = price
28
29 def search(self, movie: int) -> List[int]:
30 """
31 Search for the 5 cheapest shops that have an unrented copy of the given movie.
32
33 Args:
34 movie: The movie ID to search for
35
36 Returns:
37 List of up to 5 shop IDs with cheapest prices
38 """
39 # Return shop IDs from the first 5 (price, shop) tuples
40 return [shop for _, shop in self.unrented[movie][:5]]
41
42 def rent(self, shop: int, movie: int) -> None:
43 """
44 Rent a movie from a specific shop.
45
46 Args:
47 shop: The shop ID
48 movie: The movie ID to rent
49 """
50 # Get the price for this shop-movie combination
51 price = self.shop_and_movie_to_price[(shop, movie)]
52
53 # Remove from unrented inventory
54 self.unrented[movie].remove((price, shop))
55
56 # Add to rented inventory
57 self.rented.add((price, shop, movie))
58
59 def drop(self, shop: int, movie: int) -> None:
60 """
61 Return a rented movie to a specific shop.
62
63 Args:
64 shop: The shop ID
65 movie: The movie ID to return
66 """
67 # Get the price for this shop-movie combination
68 price = self.shop_and_movie_to_price[(shop, movie)]
69
70 # Add back to unrented inventory
71 self.unrented[movie].add((price, shop))
72
73 # Remove from rented inventory
74 self.rented.remove((price, shop, movie))
75
76 def report(self) -> List[List[int]]:
77 """
78 Get the 5 cheapest rented movies.
79
80 Returns:
81 List of up to 5 [shop_id, movie_id] pairs
82 """
83 # Return shop and movie IDs from the first 5 rented entries
84 return [[shop, movie] for _, shop, movie in self.rented[:5]]
85
86
87# Your MovieRentingSystem object will be instantiated and called as such:
88# obj = MovieRentingSystem(n, entries)
89# param_1 = obj.search(movie)
90# obj.rent(shop, movie)
91# obj.drop(shop, movie)
92# param_4 = obj.report()
93
1import java.util.*;
2
3class MovieRentingSystem {
4 // Store unrented movies: movieId -> TreeSet of (price, shopId) pairs
5 private Map<Integer, TreeSet<int[]>> unrented;
6
7 // Store price lookup: "shopId,movieId" -> price
8 private Map<String, Integer> shopAndMovieToPrice;
9
10 // Store currently rented movies: TreeSet of (price, shopId, movieId) triples
11 private TreeSet<int[]> rented;
12
13 public MovieRentingSystem(int n, int[][] entries) {
14 /**
15 * Initialize the movie renting system.
16 *
17 * @param n Number of shops
18 * @param entries Array of [shop, movie, price] entries
19 */
20
21 // Initialize data structures
22 this.unrented = new HashMap<>();
23 this.shopAndMovieToPrice = new HashMap<>();
24
25 // Custom comparator for unrented movies: sort by price, then by shop ID
26 Comparator<int[]> unrentedComparator = (a, b) -> {
27 if (a[0] != b[0]) return Integer.compare(a[0], b[0]); // Compare by price
28 return Integer.compare(a[1], b[1]); // Compare by shop ID
29 };
30
31 // Custom comparator for rented movies: sort by price, then shop ID, then movie ID
32 this.rented = new TreeSet<>((a, b) -> {
33 if (a[0] != b[0]) return Integer.compare(a[0], b[0]); // Compare by price
34 if (a[1] != b[1]) return Integer.compare(a[1], b[1]); // Compare by shop ID
35 return Integer.compare(a[2], b[2]); // Compare by movie ID
36 });
37
38 // Process initial entries
39 for (int[] entry : entries) {
40 int shop = entry[0];
41 int movie = entry[1];
42 int price = entry[2];
43
44 // Add to unrented collection
45 unrented.computeIfAbsent(movie, k -> new TreeSet<>(unrentedComparator))
46 .add(new int[]{price, shop});
47
48 // Store price for quick lookup
49 shopAndMovieToPrice.put(shop + "," + movie, price);
50 }
51 }
52
53 public List<Integer> search(int movie) {
54 /**
55 * Search for the 5 cheapest shops that have an unrented copy of the given movie.
56 *
57 * @param movie The movie ID to search for
58 * @return List of up to 5 shop IDs with cheapest prices
59 */
60
61 List<Integer> result = new ArrayList<>();
62
63 // Check if this movie exists in any shop
64 if (!unrented.containsKey(movie)) {
65 return result;
66 }
67
68 // Get up to 5 shops with cheapest prices
69 int count = 0;
70 for (int[] entry : unrented.get(movie)) {
71 if (count >= 5) break;
72 result.add(entry[1]); // Add shop ID
73 count++;
74 }
75
76 return result;
77 }
78
79 public void rent(int shop, int movie) {
80 /**
81 * Rent a movie from a specific shop.
82 *
83 * @param shop The shop ID
84 * @param movie The movie ID to rent
85 */
86
87 // Get the price for this shop-movie combination
88 int price = shopAndMovieToPrice.get(shop + "," + movie);
89
90 // Remove from unrented inventory
91 unrented.get(movie).remove(new int[]{price, shop});
92
93 // Add to rented inventory
94 rented.add(new int[]{price, shop, movie});
95 }
96
97 public void drop(int shop, int movie) {
98 /**
99 * Return a rented movie to a specific shop.
100 *
101 * @param shop The shop ID
102 * @param movie The movie ID to return
103 */
104
105 // Get the price for this shop-movie combination
106 int price = shopAndMovieToPrice.get(shop + "," + movie);
107
108 // Add back to unrented inventory
109 unrented.get(movie).add(new int[]{price, shop});
110
111 // Remove from rented inventory
112 rented.remove(new int[]{price, shop, movie});
113 }
114
115 public List<List<Integer>> report() {
116 /**
117 * Get the 5 cheapest rented movies.
118 *
119 * @return List of up to 5 [shop_id, movie_id] pairs
120 */
121
122 List<List<Integer>> result = new ArrayList<>();
123
124 // Get up to 5 cheapest rented movies
125 int count = 0;
126 for (int[] entry : rented) {
127 if (count >= 5) break;
128 result.add(Arrays.asList(entry[1], entry[2])); // Add [shop_id, movie_id]
129 count++;
130 }
131
132 return result;
133 }
134}
135
136/**
137 * Your MovieRentingSystem object will be instantiated and called as such:
138 * MovieRentingSystem obj = new MovieRentingSystem(n, entries);
139 * List<Integer> param_1 = obj.search(movie);
140 * obj.rent(shop, movie);
141 * obj.drop(shop, movie);
142 * List<List<Integer>> param_4 = obj.report();
143 */
144
1#include <vector>
2#include <unordered_map>
3#include <set>
4#include <algorithm>
5
6class MovieRentingSystem {
7private:
8 // Store unrented movies: movie_id -> sorted set of (price, shop_id)
9 std::unordered_map<int, std::set<std::pair<int, int>>> unrented;
10
11 // Store price lookup: (shop_id, movie_id) -> price
12 std::map<std::pair<int, int>, int> shopAndMovieToPrice;
13
14 // Store currently rented movies: sorted set of (price, shop_id, movie_id)
15 std::set<std::tuple<int, int, int>> rented;
16
17public:
18 /**
19 * Initialize the movie renting system.
20 * @param n Number of shops
21 * @param entries List of [shop, movie, price] entries
22 */
23 MovieRentingSystem(int n, std::vector<std::vector<int>>& entries) {
24 // Process initial entries
25 for (const auto& entry : entries) {
26 int shop = entry[0];
27 int movie = entry[1];
28 int price = entry[2];
29
30 // Add to unrented inventory
31 unrented[movie].insert({price, shop});
32
33 // Store price for quick lookup
34 shopAndMovieToPrice[{shop, movie}] = price;
35 }
36 }
37
38 /**
39 * Search for the 5 cheapest shops that have an unrented copy of the given movie.
40 * @param movie The movie ID to search for
41 * @return List of up to 5 shop IDs with cheapest prices
42 */
43 std::vector<int> search(int movie) {
44 std::vector<int> result;
45
46 // Check if the movie exists in unrented inventory
47 if (unrented.find(movie) != unrented.end()) {
48 // Get up to 5 shops with the cheapest prices
49 int count = 0;
50 for (const auto& [price, shop] : unrented[movie]) {
51 if (count >= 5) break;
52 result.push_back(shop);
53 count++;
54 }
55 }
56
57 return result;
58 }
59
60 /**
61 * Rent a movie from a specific shop.
62 * @param shop The shop ID
63 * @param movie The movie ID to rent
64 */
65 void rent(int shop, int movie) {
66 // Get the price for this shop-movie combination
67 int price = shopAndMovieToPrice[{shop, movie}];
68
69 // Remove from unrented inventory
70 unrented[movie].erase({price, shop});
71
72 // Add to rented inventory
73 rented.insert({price, shop, movie});
74 }
75
76 /**
77 * Return a rented movie to a specific shop.
78 * @param shop The shop ID
79 * @param movie The movie ID to return
80 */
81 void drop(int shop, int movie) {
82 // Get the price for this shop-movie combination
83 int price = shopAndMovieToPrice[{shop, movie}];
84
85 // Add back to unrented inventory
86 unrented[movie].insert({price, shop});
87
88 // Remove from rented inventory
89 rented.erase({price, shop, movie});
90 }
91
92 /**
93 * Get the 5 cheapest rented movies.
94 * @return List of up to 5 [shop_id, movie_id] pairs
95 */
96 std::vector<std::vector<int>> report() {
97 std::vector<std::vector<int>> result;
98
99 // Get up to 5 cheapest rented movies
100 int count = 0;
101 for (const auto& [price, shop, movie] : rented) {
102 if (count >= 5) break;
103 result.push_back({shop, movie});
104 count++;
105 }
106
107 return result;
108 }
109};
110
111/**
112 * Your MovieRentingSystem object will be instantiated and called as such:
113 * MovieRentingSystem* obj = new MovieRentingSystem(n, entries);
114 * vector<int> param_1 = obj->search(movie);
115 * obj->rent(shop,movie);
116 * obj->drop(shop,movie);
117 * vector<vector<int>> param_4 = obj->report();
118 */
119
1// Import statement for SortedList-like functionality would be needed
2// For TypeScript, we'll use arrays with manual sorting
3
4// Store unrented movies: movie_id -> array of [price, shop_id] tuples
5let unrented: Map<number, Array<[number, number]>> = new Map();
6
7// Store price lookup: "shop_id,movie_id" -> price
8let shopAndMovieToPrice: Map<string, number> = new Map();
9
10// Store currently rented movies: array of [price, shop_id, movie_id] tuples
11let rented: Array<[number, number, number]> = [];
12
13/**
14 * Initialize the movie renting system
15 * @param n - Number of shops
16 * @param entries - Array of [shop, movie, price] entries
17 */
18function MovieRentingSystem(n: number, entries: number[][]): void {
19 // Clear existing data
20 unrented = new Map();
21 shopAndMovieToPrice = new Map();
22 rented = [];
23
24 // Process initial entries
25 for (const [shop, movie, price] of entries) {
26 // Initialize array for movie if not exists
27 if (!unrented.has(movie)) {
28 unrented.set(movie, []);
29 }
30
31 // Add to unrented inventory and sort
32 const movieList = unrented.get(movie)!;
33 movieList.push([price, shop]);
34 // Sort by price first, then by shop ID
35 movieList.sort((a, b) => a[0] !== b[0] ? a[0] - b[0] : a[1] - b[1]);
36
37 // Store price for shop-movie combination
38 const key = `${shop},${movie}`;
39 shopAndMovieToPrice.set(key, price);
40 }
41}
42
43/**
44 * Search for the 5 cheapest shops that have an unrented copy of the given movie
45 * @param movie - The movie ID to search for
46 * @returns Array of up to 5 shop IDs with cheapest prices
47 */
48function search(movie: number): number[] {
49 // Get unrented list for this movie
50 const movieList = unrented.get(movie) || [];
51
52 // Return shop IDs from the first 5 [price, shop] tuples
53 const result: number[] = [];
54 for (let i = 0; i < Math.min(5, movieList.length); i++) {
55 result.push(movieList[i][1]);
56 }
57 return result;
58}
59
60/**
61 * Rent a movie from a specific shop
62 * @param shop - The shop ID
63 * @param movie - The movie ID to rent
64 */
65function rent(shop: number, movie: number): void {
66 // Get the price for this shop-movie combination
67 const key = `${shop},${movie}`;
68 const price = shopAndMovieToPrice.get(key)!;
69
70 // Remove from unrented inventory
71 const movieList = unrented.get(movie)!;
72 const indexToRemove = movieList.findIndex(item => item[0] === price && item[1] === shop);
73 movieList.splice(indexToRemove, 1);
74
75 // Add to rented inventory
76 rented.push([price, shop, movie]);
77 // Sort rented list by price, then shop ID, then movie ID
78 rented.sort((a, b) => {
79 if (a[0] !== b[0]) return a[0] - b[0];
80 if (a[1] !== b[1]) return a[1] - b[1];
81 return a[2] - b[2];
82 });
83}
84
85/**
86 * Return a rented movie to a specific shop
87 * @param shop - The shop ID
88 * @param movie - The movie ID to return
89 */
90function drop(shop: number, movie: number): void {
91 // Get the price for this shop-movie combination
92 const key = `${shop},${movie}`;
93 const price = shopAndMovieToPrice.get(key)!;
94
95 // Add back to unrented inventory
96 const movieList = unrented.get(movie)!;
97 movieList.push([price, shop]);
98 // Re-sort the unrented list
99 movieList.sort((a, b) => a[0] !== b[0] ? a[0] - b[0] : a[1] - b[1]);
100
101 // Remove from rented inventory
102 const indexToRemove = rented.findIndex(
103 item => item[0] === price && item[1] === shop && item[2] === movie
104 );
105 rented.splice(indexToRemove, 1);
106}
107
108/**
109 * Get the 5 cheapest rented movies
110 * @returns Array of up to 5 [shop_id, movie_id] pairs
111 */
112function report(): number[][] {
113 // Return shop and movie IDs from the first 5 rented entries
114 const result: number[][] = [];
115 for (let i = 0; i < Math.min(5, rented.length); i++) {
116 result.push([rented[i][1], rented[i][2]]);
117 }
118 return result;
119}
120
Time and Space Complexity
Time Complexity:
-
__init__(n, entries)
:O(m * log m)
wherem
is the number of entries. Each entry insertion into the SortedList takesO(log m)
time, and we process allm
entries. -
search(movie)
:O(1)
to access the unrented movies for a specific movie, andO(1)
to retrieve up to 5 elements from the sorted list. -
rent(shop, movie)
:O(log k + log r)
wherek
is the number of shops having the specific movie andr
is the number of currently rented movies. The operation includes:O(1)
to look up the priceO(log k)
to remove fromself.unrented[movie]
O(log r)
to add toself.rented
-
drop(shop, movie)
:O(log k + log r)
wherek
is the number of shops having the specific movie andr
is the number of currently rented movies. The operation includes:O(1)
to look up the priceO(log k)
to add toself.unrented[movie]
O(log r)
to remove fromself.rented
-
report()
:O(1)
to retrieve up to 5 elements from the sorted list.
Space Complexity:
O(m)
where m
is the total number of entries. The space is used for:
self.unrented
: stores all unrented movie-shop pairs, up toO(m)
entriesself.shopAndMovieToPrice
: stores all shop-movie combinations with their prices, exactlyO(m)
entriesself.rented
: stores currently rented movies, at mostO(m)
entries
The total space complexity is O(m)
as we store each entry in multiple data structures but the total is still proportional to the number of entries.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Tuple Ordering for Sorting
Pitfall: One of the most common mistakes is using the wrong order of elements in tuples, which leads to incorrect sorting behavior.
Wrong Implementation:
# Incorrect: Using (shop, price) instead of (price, shop) self.unrented[movie].add((shop, price)) # Wrong order! # This would sort by shop ID first, then price - opposite of what we want
Why it's problematic: Python tuples are compared element by element from left to right. If we put shop ID before price, the system would return shops sorted by their ID rather than by price, completely breaking the "cheapest first" requirement.
Correct Solution:
# Correct: Price must come first for proper sorting self.unrented[movie].add((price, shop)) # Price first, shop second # For rented items, we need even more careful ordering self.rented.add((price, shop, movie)) # Sorts by price, then shop, then movie
2. Not Handling the SortedList Import Dependency
Pitfall: Forgetting that SortedList
is not a built-in Python data structure and requires the sortedcontainers
library.
Problem Code:
from sortedcontainers import SortedList # This might fail if not installed
Solution: If sortedcontainers
is not available, you can implement a workaround using built-in structures:
import heapq
from collections import defaultdict
class MovieRentingSystem:
def __init__(self, n: int, entries: List[List[int]]):
# Use regular lists and sort when needed
self.unrented = defaultdict(list)
self.shop_and_movie_to_price = {}
self.rented = []
for shop, movie, price in entries:
heapq.heappush(self.unrented[movie], (price, shop))
self.shop_and_movie_to_price[(shop, movie)] = price
def search(self, movie: int) -> List[int]:
# Create a sorted copy without modifying the heap
temp = sorted(self.unrented[movie])
return [shop for _, shop in temp[:5]]
def rent(self, shop: int, movie: int) -> None:
price = self.shop_and_movie_to_price[(shop, movie)]
# Mark as rented instead of removing (lazy deletion)
# Would need additional tracking to handle this properly
heapq.heappush(self.rented, (price, shop, movie))
3. Forgetting to Store Prices for Later Lookup
Pitfall: Not maintaining the shop_and_movie_to_price
dictionary, making it impossible to retrieve prices during rent/drop operations.
Wrong Implementation:
def __init__(self, n: int, entries: List[List[int]]):
self.unrented = defaultdict(SortedList)
self.rented = SortedList()
for shop, movie, price in entries:
self.unrented[movie].add((price, shop))
# Forgot to store: self.shop_and_movie_to_price[(shop, movie)] = price
def rent(self, shop: int, movie: int) -> None:
# Now we can't get the price!
price = ??? # No way to retrieve this
Correct Solution: Always store the price mapping during initialization:
self.shop_and_movie_to_price[(shop, movie)] = price
4. Assuming Movies are Always Available for Operations
Pitfall: Not considering edge cases where a movie might not exist or operations might be called on already rented/unrented movies.
Defensive Solution:
def rent(self, shop: int, movie: int) -> None:
# Add validation
if (shop, movie) not in self.shop_and_movie_to_price:
return # Movie doesn't exist at this shop
price = self.shop_and_movie_to_price[(shop, movie)]
# Check if actually available to rent
if (price, shop) in self.unrented[movie]:
self.unrented[movie].remove((price, shop))
self.rented.add((price, shop, movie))
5. Inefficient Search Without Early Termination
Pitfall: Creating unnecessary copies or sorting entire lists when only 5 elements are needed.
Inefficient:
def search(self, movie: int) -> List[int]:
# Sorts entire list even if we only need 5 elements
all_shops = sorted([(price, shop) for price, shop in self.unrented[movie]])
return [shop for _, shop in all_shops[:5]]
Efficient Solution: The SortedList maintains order, so slicing [:5]
is O(1) and doesn't require re-sorting:
return [shop for _, shop in self.unrented[movie][:5]]
How many ways can you arrange the three letters A, B and C?
Recommended Readings
https assets algo monster cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue is a data structure
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!