查看: 575| 回复: 1
跳转到指定楼层
上一主题 下一主题
收起左侧

[高频题] [面经] NVIDIA Coding:LRU 磁盘空间管理器(按容量淘汰)

全局:

注册一亩三分地论坛,查看更多干货!

您需要 登录 才可以下载或查看附件。没有帐号?注册账号

x
[面经] NVIDIA Coding:LRU 磁盘空间管理器(按容量淘汰)
最近面了 NVIDIA,分享一下技术电面里的一道题。表面上看就是 LRU 缓存,但有个关键区别:容量限制的是总大小,不是条数。这一点同时影响淘汰逻辑和更新路径,很适合检验 LRU 的基本功到底是真的扎实还是背下来的。面试官一开始没有限定淘汰策略(合理的策略都可以,比如 LRU),后来为了让结果确定,固定用 LRU,操作也以批量形式给出、按顺序处理。
题目
实现一个内存里的磁盘空间管理器,总容量固定。数据集用
  1. datasetId
复制代码
标识,每个有一个整数
  1. size
复制代码
。
    1. put(datasetId, size)
    复制代码
    :存入一个数据集。空闲空间不够时,淘汰最久未使用的数据集,直到能放下为止。如果
    1. size > totalCapacity
    复制代码
    ,这次 put 失败,并且状态必须保持原样。
    1. get(datasetId)
    复制代码
    :返回存的大小,不存在返回 -1。
细节规则:
  • 每次成功的
    1. get
    复制代码
    和每次成功的
    1. put
    复制代码
    (包括更新)都让该数据集变为最近使用。
    1. put
    复制代码
    一个已存在的
    1. datasetId
    复制代码
    是替换它的大小。先释放旧大小,再决定要不要淘汰别人。
  • 为了可测试,操作以批量形式给出:
    1. solution(total_capacity, operations)
    复制代码
    按顺序返回每个操作的结果。put 成功返回
    1. "ok"
    复制代码
    ,失败返回
    1. "error"
    复制代码
    ,get 返回大小或 -1。
约束:
  1. total_capacity
复制代码
最大 1e9,操作数最多 2e5,
  1. size
复制代码
在 1 到 1e9 之间,
  1. datasetId
复制代码
是非空字符串。
例题走一遍
容量 10,操作序列:put A 4, put B 3, get A, put C 5, get B, get A, get C。
  • put A (4):放得下。used = 4。新旧顺序:A
  • put B (3):放得下。used = 7。顺序:A, B
  • get A:返回 4,A 变为最近使用。顺序:B, A
  • put C (5):空闲只有 3,不够。B 是最久未使用的,先淘汰 B(used = 4),再放入 C(used = 9)。顺序:A, C
  • get B:-1,B 已经被淘汰了
  • get A:4
  • get C:5
输出:
  1. ['ok', 'ok', 4, 'ok', -1, 4, 5]
复制代码
最容易错的一步在第 4 步:被淘汰的是 B 不是 A,因为第 3 步的 get 刷新了 A。
核心思路
需要同时做到两件事:按
  1. datasetId
复制代码
O(1) 查找,以及 O(1) 拿到最久未使用的数据集。这就是 LRU 缓存经典的哈希表 + 有序结构组合。和教科书版 LRU 缓存的区别有三点:
  • 淘汰是一个循环,不是一次弹出:一次 put 可能淘汰多个数据集。
    1. used
    复制代码
    的记账要经得起更新:先减掉旧大小再淘汰,否则会淘汰不必要的数据集,更糟的是可能把正在更新的数据集自己淘汰掉。
  • put 只有一种失败方式:
    1. size > capacity
    复制代码
    。只要它本身能放进总容量,把其他都淘汰掉总能腾出地方,没有第二种失败路径。
方法 A:OrderedDict
  1. from collections import OrderedDict
  2. class DiskSpaceManager:
  3.     def __init__(self, total_capacity):
  4.         self.capacity = total_capacity
  5.         self.used = 0
  6.         self.store = OrderedDict()  # datasetId -> size, LRU first
  7.     def get(self, dataset_id):
  8.         if dataset_id not in self.store:
  9.             return -1
  10.         self.store.move_to_end(dataset_id)  # now most recently used
  11.         return self.store[dataset_id]
  12.     def put(self, dataset_id, size):
  13.         if size > self.capacity:
  14.             return "error"  # can never fit; state untouched
  15.         if dataset_id in self.store:
  16.             # Update: free the old size before evicting anyone else.
  17.             self.used -= self.store.pop(dataset_id)
  18.         while self.used + size > self.capacity:
  19.             _, evicted_size = self.store.popitem(last=False)  # LRU end
  20.             self.used -= evicted_size
  21.         self.store[dataset_id] = size
  22.         self.used += size
  23.         return "ok"
  24. def solution(total_capacity, operations):
  25.     mgr = DiskSpaceManager(total_capacity)
  26.     out = []
  27.     for op in operations:
  28.         if op[0] == "put":
  29.             out.append(mgr.put(op[1], op[2]))
  30.         else:
  31.             out.append(mgr.get(op[1]))
  32.     return out
复制代码
方法 B:哈希表 + 双向链表
同样的设计,但不用库来维护顺序:一个从
  1. datasetId
复制代码
到节点的字典,再加一条带头尾哨兵的双向链表保存新旧顺序(靠近头的是最久未使用,靠近尾的是最近使用)。适合 Java 里没有 access-order
  1. LinkedHashMap
复制代码
的情况,或者面试官想看链表本身实现的时候。
  1. class _Node:
  2.     __slots__ = ("key", "size", "prev", "next")
  3.     def __init__(self, key=None, size=0):
  4.         self.key = key
  5.         self.size = size
  6.         self.prev = None
  7.         self.next = None
  8. class DiskSpaceManagerDLL:
  9.     def __init__(self, total_capacity):
  10.         self.capacity = total_capacity
  11.         self.used = 0
  12.         self.map = {}
  13.         self.head = _Node()  # sentinel: head.next is the LRU dataset
  14.         self.tail = _Node()  # sentinel: tail.prev is the MRU dataset
  15.         self.head.next = self.tail
  16.         self.tail.prev = self.head
  17.     def _unlink(self, node):
  18.         node.prev.next = node.next
  19.         node.next.prev = node.prev
  20.     def _append_mru(self, node):
  21.         last = self.tail.prev
  22.         last.next = node
  23.         node.prev = last
  24.         node.next = self.tail
  25.         self.tail.prev = node
  26.     def get(self, dataset_id):
  27.         node = self.map.get(dataset_id)
  28.         if node is None:
  29.             return -1
  30.         self._unlink(node)
  31.         self._append_mru(node)
  32.         return node.size
  33.     def put(self, dataset_id, size):
  34.         if size > self.capacity:
  35.             return "error"
  36.         old = self.map.pop(dataset_id, None)
  37.         if old is not None:
  38.             self._unlink(old)
  39.             self.used -= old.size
  40.         while self.used + size > self.capacity:
  41.             lru = self.head.next
  42.             self._unlink(lru)
  43.             del self.map[lru.key]
  44.             self.used -= lru.size
  45.         node = _Node(dataset_id, size)
  46.         self.map[dataset_id] = node
  47.         self._append_mru(node)
  48.         self.used += size
  49.         return "ok"
复制代码
复杂度
    1. get
    复制代码
    :平均 O(1)。
    1. put
    复制代码
    :平均 O(1) 加上淘汰的开销。每个数据集每次插入后最多被淘汰一次,所以 n 个操作总共是 O(n);淘汰循环是均摊的,不会变成每个 put 都二次方。
  • 空间:O(当前存着的数据集数量)。
易错点
  • 失败的 put 什么都不能改。
    1. size > capacity
    复制代码
    时不要淘汰、不要插入、不要动新旧顺序。更新的情况也一样:超大的更新要让旧数据集保留旧大小和旧位置。
  • 先释放旧大小,再淘汰。更新时如果先淘汰后记账,会多淘汰不必要的数据集,还可能把正在更新的数据集自己淘汰掉。
  • get 未命中不算一次使用。返回 -1 时不能创建条目,也不能改新旧顺序。
  • 一旦新数据集能放下就停止淘汰。多淘汰一个是最常见的 off-by-one;上面的例题能抓到它(第 4 步如果连 A 一起淘汰,put 照样成功,但第 6 步就会错误地返回 -1)。
验证:除了上面的例题,还用 500 组随机操作序列和一个朴素 O(n) 参考实现做了对拍,两种方法全部一致。
觉得有帮助的话欢迎收藏、加米。也欢迎讨论变体,比如把 LRU 换成 LFU,或者给数据集加 TTL。

评分

参与人数 1大米 +1 收起 理由
匿名用户-KJ39A + 1 给你点个赞!

查看全部评分


上一篇:[面经] Meta Coding:没有地图的迷宫找奶酪(DFS + 回溯)
下一篇:《Meta面试:2026新流程考题清单》
全局:
谢谢
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册账号
隐私提醒:
  • ☑ 禁止发布广告,拉群,贴个人联系方式:找人请去🔗同学同事飞友,拉群请去🔗拉群结伴,广告请去🔗跳蚤市场,和 🔗租房广告|找室友
  • ☑ 论坛内容在发帖 30 分钟内可以编辑,过后则不能删帖。为防止被骚扰甚至人肉,不要公开留微信等联系方式,如有需求请以论坛私信方式发送。
  • ☑ 干货版块可免费使用 🔗超级匿名:面经(美国面经、中国面经、数科面经、PM面经),抖包袱(美国、中国)和录取汇报、定位选校版
  • ☑ 查阅全站 🔗各种匿名方法

本版积分规则

>
快速回复 返回顶部 返回列表