Facebook Pixel

1912. Design Movie Rental System

HardDesignArrayHash TableOrdered SetHeap (Priority Queue)
Leetcode Link

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:

  1. 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
  2. Rent(shop, movie): Mark a specific movie copy at a specific shop as rented (removing it from available inventory).

  3. Drop(shop, movie): Return a previously rented movie to a specific shop (making it available again).

  4. 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 copies
  • shopAndMovieToPrice: A dictionary storing the price for each (shop, movie) combination
  • rented: 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. A way to group available movies by movie ID (since search is by movie ID)
  2. A way to track all rented movies together (since report looks across all movies)
  3. 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

  1. unrented: A defaultdict(SortedList) that maps each movie ID to a sorted list of (price, shop) tuples representing available copies
  2. rented: A SortedList containing (price, shop, movie) tuples for all currently rented movies
  3. shopAndMovieToPrice: 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 Evaluator

Example 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 5,Movie2at5, Movie 2 at 3
  • Shop 1 has: Movie 1 at 4,Movie2at4, Movie 2 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: 4,4, 5, $7)

Operation 2: Rent(shop=1, movie=1)

  • Look up price: shopAndMovieToPrice[(1, 1)] = 4
  • Remove (4, 1) from unrented[1]
  • Add (4, 1, 1) to rented

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) from unrented[2]
  • Add (3, 0, 2) to rented

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: 3,3, 4)

Operation 5: Drop(shop=0, movie=2)

  • Look up price: shopAndMovieToPrice[(0, 2)] = 3
  • Add (3, 0) back to unrented[2]
  • Remove (3, 0, 2) from rented

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: 3,3, 6)

This example demonstrates how:

  1. The SortedList structures automatically maintain sort order during insertions and removals
  2. The tuple structure (price, shop) or (price, shop, movie) provides natural sorting
  3. The shopAndMovieToPrice dictionary enables quick price lookups during rent/drop operations
  4. Search operations are fast because we can directly access the sorted list for a specific movie
  5. 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) where m is the number of entries. Each entry insertion into the SortedList takes O(log m) time, and we process all m entries.

  • search(movie): O(1) to access the unrented movies for a specific movie, and O(1) to retrieve up to 5 elements from the sorted list.

  • rent(shop, movie): O(log k + log r) where k is the number of shops having the specific movie and r is the number of currently rented movies. The operation includes:

    • O(1) to look up the price
    • O(log k) to remove from self.unrented[movie]
    • O(log r) to add to self.rented
  • drop(shop, movie): O(log k + log r) where k is the number of shops having the specific movie and r is the number of currently rented movies. The operation includes:

    • O(1) to look up the price
    • O(log k) to add to self.unrented[movie]
    • O(log r) to remove from self.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 to O(m) entries
  • self.shopAndMovieToPrice: stores all shop-movie combinations with their prices, exactly O(m) entries
  • self.rented: stores currently rented movies, at most O(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]]
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

How many ways can you arrange the three letters A, B and C?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More