The "Design Movie Rental System" problem requires you to implement a class that simulates a movie rental service across multiple shops. Each shop has a set of movies, each with a rental price. The system must support the following operations:
search(movie)
: Return up to 5 shops with the lowest price for a given movie
that is currently available (not rented), sorted by price, then shop id.rent(shop, movie)
: Mark the movie
as rented from the given shop
.drop(shop, movie)
: Mark the movie
as returned to the given shop
.report()
: Return up to 5 currently rented movies, sorted by price, then shop id, then movie id. Each entry is a tuple [shop, movie]
.
You are given the initial inventory as a list of [shop, movie, price]
entries. Each shop
can have multiple movies, and prices may vary per shop. You must ensure all operations are efficient and maintain the correct orderings.
Constraints:
shop
and movie
pair.
At first glance, we might consider storing all movies in a simple list or dictionary and scanning through them for each operation. However, this would be inefficient, especially for the search
and report
operations, which require sorted results and must be fast.
We need a way to:
search
).report
).SortedList
(from sortedcontainers
) in Python, TreeSet
in Java, set
in C++ (with custom comparators), and arrays with binary search in JavaScript.
Let's break down the design step by step:
movie
, maintain a sorted list of available copies across shops, sorted by price and shop id.
(shop, movie)
to price
for quick lookups.
(price, shop)
from the available list for that movie.(price, shop, movie)
to the global rented list.(price, shop, movie)
from the rented list.(price, shop)
back to the available list for that movie.[shop, movie]
.These steps ensure all operations are efficient and results are always sorted as required.
Suppose the initial inventory is:
[0, 1, 5]
(Shop 0 has Movie 1 for $5)[0, 2, 6]
(Shop 0 has Movie 2 for $6)[1, 1, 4]
(Shop 1 has Movie 1 for $4)[2, 1, 4]
(Shop 2 has Movie 1 for $4)search
or report
would scan all entries: O(N) per call.rent
and drop
would also be O(N) due to searching for the correct entry.The use of sorted data structures ensures that both searching and updating operations remain efficient even as the number of shops and movies grows.
The key to solving the Movie Rental System problem efficiently is to use data structures that maintain sorted order and allow for fast insertions and deletions. By keeping per-movie available lists and a global rented list, and updating these as rentals and drops occur, we can support all required operations in logarithmic time. This approach is both elegant and practical, leveraging the strengths of balanced trees or sorted lists to maintain order and efficiency.
from collections import defaultdict
from sortedcontainers import SortedList
class MovieRentingSystem:
def __init__(self, n, entries):
# (shop, movie) -> price
self.price_map = {}
# movie -> SortedList[(price, shop)]
self.available = defaultdict(SortedList)
# SortedList[(price, shop, movie)]
self.rented = SortedList()
for shop, movie, price in entries:
self.price_map[(shop, movie)] = price
self.available[movie].add((price, shop))
def search(self, movie):
# Return up to 5 shops with lowest price for movie
return [shop for price, shop in self.available[movie][:5]]
def rent(self, shop, movie):
price = self.price_map[(shop, movie)]
self.available[movie].remove((price, shop))
self.rented.add((price, shop, movie))
def drop(self, shop, movie):
price = self.price_map[(shop, movie)]
self.rented.remove((price, shop, movie))
self.available[movie].add((price, shop))
def report(self):
# Return up to 5 rented movies as [shop, movie]
return [[shop, movie] for price, shop, movie in self.rented[:5]]
#include <vector>
#include <map>
#include <set>
#include <tuple>
using namespace std;
class MovieRentingSystem {
map<pair<int, int>, int> price_map;
map<int, set<pair<int, int>>> available;
set<tuple<int, int, int>> rented;
public:
MovieRentingSystem(int n, vector<vector<int>>& entries) {
for (auto& e : entries) {
int shop = e[0], movie = e[1], price = e[2];
price_map[{shop, movie}] = price;
available[movie].insert({price, shop});
}
}
vector<int> search(int movie) {
vector<int> res;
auto& av = available[movie];
for (auto it = av.begin(); it != av.end() && res.size() < 5; ++it)
res.push_back(it->second);
return res;
}
void rent(int shop, int movie) {
int price = price_map[{shop, movie}];
available[movie].erase({price, shop});
rented.insert({price, shop, movie});
}
void drop(int shop, int movie) {
int price = price_map[{shop, movie}];
rented.erase({price, shop, movie});
available[movie].insert({price, shop});
}
vector<vector<int>> report() {
vector<vector<int>> res;
int cnt = 0;
for (auto it = rented.begin(); it != rented.end() && cnt < 5; ++it, ++cnt)
res.push_back({get<1>(*it), get<2>(*it)});
return res;
}
};
import java.util.*;
class MovieRentingSystem {
Map<Pair, Integer> priceMap = new HashMap<>();
Map<Integer, TreeSet<ShopPrice>> available = new HashMap<>();
TreeSet<RentedMovie> rented = new TreeSet<>();
static class Pair {
int shop, movie;
Pair(int s, int m) { shop = s; movie = m; }
public boolean equals(Object o) {
if (!(o instanceof Pair)) return false;
Pair p = (Pair)o;
return shop == p.shop && movie == p.movie;
}
public int hashCode() { return shop * 10007 + movie; }
}
static class ShopPrice implements Comparable<ShopPrice> {
int price, shop;
ShopPrice(int p, int s) { price = p; shop = s; }
public int compareTo(ShopPrice o) {
if (price != o.price) return price - o.price;
return shop - o.shop;
}
}
static class RentedMovie implements Comparable<RentedMovie> {
int price, shop, movie;
RentedMovie(int p, int s, int m) { price = p; shop = s; movie = m; }
public int compareTo(RentedMovie o) {
if (price != o.price) return price - o.price;
if (shop != o.shop) return shop - o.shop;
return movie - o.movie;
}
}
public MovieRentingSystem(int n, int[][] entries) {
for (int[] e : entries) {
int shop = e[0], movie = e[1], price = e[2];
priceMap.put(new Pair(shop, movie), price);
available.computeIfAbsent(movie, k -> new TreeSet<>()).add(new ShopPrice(price, shop));
}
}
public List<Integer> search(int movie) {
List<Integer> res = new ArrayList<>();
TreeSet<ShopPrice> set = available.get(movie);
if (set == null) return res;
int cnt = 0;
for (ShopPrice sp : set) {
if (cnt++ == 5) break;
res.add(sp.shop);
}
return res;
}
public void rent(int shop, int movie) {
int price = priceMap.get(new Pair(shop, movie));
available.get(movie).remove(new ShopPrice(price, shop));
rented.add(new RentedMovie(price, shop, movie));
}
public void drop(int shop, int movie) {
int price = priceMap.get(new Pair(shop, movie));
rented.remove(new RentedMovie(price, shop, movie));
available.get(movie).add(new ShopPrice(price, shop));
}
public List<List<Integer>> report() {
List<List<Integer>> res = new ArrayList<>();
int cnt = 0;
for (RentedMovie rm : rented) {
if (cnt++ == 5) break;
res.add(Arrays.asList(rm.shop, rm.movie));
}
return res;
}
}
class MovieRentingSystem {
constructor(n, entries) {
this.priceMap = new Map();
this.available = new Map(); // movieId -> array of [price, shop]
this.rented = []; // array of [price, shop, movie]
for (const [shop, movie, price] of entries) {
this.priceMap.set(shop + ',' + movie, price);
if (!this.available.has(movie)) this.available.set(movie, []);
this.available.get(movie).push([price, shop]);
}
// Sort each movie's available list
for (const arr of this.available.values()) {
arr.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
}
}
search(movie) {
let arr = this.available.get(movie) || [];
return arr.slice(0, 5).map(x => x[1]);
}
rent(shop, movie) {
let price = this.priceMap.get(shop + ',' + movie);
let arr = this.available.get(movie);
for (let i = 0; i < arr.length; ++i) {
if (arr[i][1] === shop && arr[i][0] === price) {
arr.splice(i, 1);
break;
}
}
this.rented.push([price, shop, movie]);
this.rented.sort((a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2]);
}
drop(shop, movie) {
let price = this.priceMap.get(shop + ',' + movie);
for (let i = 0; i < this.rented.length; ++i) {
let [p, s, m] = this.rented[i];
if (s === shop && m === movie && p === price) {
this.rented.splice(i, 1);
break;
}
}
let arr = this.available.get(movie);
arr.push([price, shop]);
arr.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
}
report() {
return this.rented.slice(0, 5).map(x => [x[1], x[2]]);
}
}