LC.1912 |设计电影租借系统|哈希|数据结构组合实战
核心思想
这道题的本质不是算法,而是数据结构的组合应用。
用 unordered_map 实现 O(1) 定位(价格查询)
用 set 实现自动排序(按价格 + ID 取前 k)
状态改变用物理搬运(erase + insert),杜绝遍历
关键约束:数据量可达 10^5 级,任何涉及 O(N) 遍历的操作都会超时。必须保证每个操作都是 O(logN) 或 O(1)。
题目描述
实现一个电影租借系统:
-
MovieRentingSystem(int n, vector<vector>& entries):初始化,entries[i] = [shop, movie, price] 表示该商店有该电影的拷贝,租金为 price(每个商店最多一份该电影)。
-
vector search(int movie):返回指定电影当前可租的最便宜 5 个商店(按价格升序,同价格按商店 ID 升序)。
-
void rent(int shop, int movie):租出指定商店的指定电影。
-
void drop(int shop, int movie):归还。
-
vector<vector> report():返回全系统已租出的最便宜 5 部电影(按价格升序,同价格按商店 ID 升序,再同商店按电影 ID 升序)。
数据结构选择
class MovieRentingSystem {
private:
// 1. 未借出索引:movie -> {price, shop}
// 利用 set 自动按 price 升序,price 相同按 shop 升序
unordered_map<int, set<pair<int, int>>> unrented;
// 2. 价格速查表:shop -> {movie -> price}
// 初始化时一次性存好,作为后续所有操作的“唯一价格来源”
unordered_map<int, unordered_map<int, int>> priceMap;
// 3. 已借出全局账本:{price, {shop, movie}}
// set 自动按 price -> shop -> movie 升序,满足 report 要求
set<pair<int, pair<int, int>>> rented;
public:
MovieRentingSystem(int n, vector<vector<int>>& entries);
vector<int> search(int movie);
void rent(int shop, int movie);
void drop(int shop, int movie);
vector<vector<int>> report();
};
方法实现
初始化
MovieRentingSystem(int n, vector<vector<int>>& entries) {
for (auto& e : entries) {
int shop = e[0], movie = e[1], price = e[2];
unrented[movie].insert({price, shop});
priceMap[shop][movie] = price;
}
}
search(movie)
vector<int> search(int movie) {
vector<int> ans;
if (unrented.count(movie) == 0) return ans;
auto& s = unrented[movie]; // 引用,避免深拷贝
for (auto it = s.begin(); it != s.end() && ans.size() < 5; ++it) {
ans.push_back(it->second); // it->second 即 shop
}
return ans;
}
rent(shop, movie)
void rent(int shop, int movie) {
int price = priceMap[shop][movie]; // O(1) 获取价格
unrented[movie].erase({price, shop}); // 从未借出集合移除
rented.insert({price, {shop, movie}}); // 加入已借出账本
}
drop(shop, movie)
void drop(int shop, int movie) {
int price = priceMap[shop][movie];
rented.erase({price, {shop, movie}}); // 从已借出账本移除
unrented[movie].insert({price, shop}); // 放回未借出集合
}
report()
vector<vector<int>> report() {
vector<vector<int>> ans;
for (auto it = rented.begin(); it != rented.end() && ans.size() < 5; ++it) {
ans.push_back({it->second.first, it->second.second}); // {shop, movie}
}
return ans;
}
复杂度分析
时间复杂度
- 初始化 O(N log M) N 为条目数,M 为每部电影平均店铺数
- search O(5) = O(1) 取前 5 个,set 内部已排序
- rent/drop O(log M) + O(log R) 各涉及一次 set 删除和一次全局 set 插入/删除
- report O(5) = O(1) 直接取全局有序 set 的前 5 个
空间复杂度
- O(N),存储所有条目。
基础加固|C++ 三大容器特性速查
1. unordered_map —— 哈希表
| 特性 | 说明 |
|---|---|
| 底层 | 哈希表 |
| 有序性 | 无序,不保证任何顺序 |
| 查找/插入/删除 | 平均 O(1),最坏 O(N) |
| 键唯一性 | 键不能重复 |
| 主要用途 | 快速通过键定位值,建立索引 |
常用操作:
unordered_map<int, string> m;
m[1] = "apple"; // 插入/覆盖
m.insert({2, "banana"}); // 若键已存在则忽略
if (m.count(1)) { ... } // 存在返回 1,不存在返回 0,不创建空键
auto it = m.find(2); // 返回迭代器,若不存在则为 m.end()
m.erase(1); // 按键删除
for (auto& [k, v] : m) { ... } // C++17 结构化绑定遍历
2. set —— 红黑树
| 特性 | 说明 |
|---|---|
| 底层 | 红黑树(平衡二叉搜索树) |
| 有序性 | 自动按升序排列 |
| 查找/插入/删除 | O(log N) |
| 元素唯一性 | 元素不能重复 |
| 主要用途 | 维护自动排序的集合,取最值、前 k 个 |
默认排序规则:
- 基本类型:按 < 升序。
- pair:先比 first,再比 second(字典序)。
- 自定义类型:需重载 operator< 或传入比较器。
常用操作:
set<pair<int, int>> s;
s.insert({3, 1}); // 自动排序
if (s.count({3, 1})) { ... } // 存在返回 1
auto it = s.find({3, 1});
s.erase({3, 1}); // 按值删除
auto smallest = *s.begin();
auto largest = *s.rbegin();
for (auto it = s.begin(); it != s.end() && k--; ++it) { ... } // 取前 k 个
3. pair —— 二元组
| 特性 | 说明 |
|---|---|
| 定义 | pair<T1, T2> |
| 成员 | first(类型 T1),second(类型 T2) |
| 比较 | 先比 first,再比 second |
| 主要用途 | 作为 set 或 map 的元素,组合多个维度 |
构造方式:
pair<int, string> p1 = {1, "apple"};
pair<int, string> p2 = make_pair(2, "banana");
pair<int, string> p3(3, "cherry");
嵌套访问:
pair<int, pair<int, int>> p = {5, {1, 2}};
int a = p.first; // 5
int b = p.second.first; // 1
int c = p.second.second; // 2
4. 三种结构组合时的排序规则(以本题为例)
| 组合 | 排序规则 |
|---|---|
| set<pair<int, int>> | 先按 first 升序,first 相同再按 second 升序 |
| set<pair<int, pair<int, int>>> | 先按最外层 first 升序,再按内层 pair 的 first,再按内层 second |
| unordered_map<int, set<pair<int, int>>> | 外层无序,内层 set 保持有序 |
本题中利用的默认排序:
- unrented[movie] 存 {price, shop} → 自动按价格升序,价格相同按商店 ID 升序,完美匹配 search 要求。
- rented 存 {price, {shop, movie}} → 先按价格,再按商店,再按电影,完美匹配 report 要求。
5. 性能陷阱与避坑指南
| 陷阱 | 正确做法 |
|---|---|
| 深拷贝 set | 用 auto& 引用,不用 auto 拷贝 |
| 查询时用 [] 导致插入空键 | 用 count() 或 find() 判断存在性 |
| 在 set 中按非第一维度删除 | 预先建立辅助索引(如 priceMap)获取完整键值 |
| 在 unordered_map 中按值查找键 | 做不到,需额外维护反向映射 |
总结
- 核心是数据结构组合:哈希表做 O(1) 索引,红黑树做自动排序,物理搬运代替标记位。
- 严禁遍历:数据量大时必须保证每个操作 O(log N) 或 O(1)。
- 状态即容器:借出/归还不是改标志位,而是把数据从一个容器移到另一个容器。
- 辅助索引是王道:用 priceMap 解决“只知道 shop 和 movie,需要价格”的痛点。
掌握这三种容器的组合用法,这道 Hard 题就变成纯粹的代码实现题。


被折叠的 条评论
为什么被折叠?



