LC.1912 |设计电影租借系统|哈希|数据结构组合实战

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 题就变成纯粹的代码实现题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值