题目重点:按 OCI 题目列出的 set、list_push、get 与 list_remove 语义构建类型化存储,明确正负 count 方向和 TTL 暂缓。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 原题要求 Golang;下方 Python 是独立可运行的语义对照,接口逻辑可迁移到 Go 的 tagged union 或带类型结构,不声称是题目语言实现。
- set 可覆盖任意原值;list_push 遇字符串键报类型错误,缺失键创建列表;list_remove 缺失键返回 0,字符串键报类型错误。题目未明确错误行为,这些是训练约定。
- get 返回列表副本,防止调用者绕过 API 修改存储;不实现 TTL,因为题目明确标注 deferred。
解题路线
- map 的 value 用字符串或列表两种类型表示;生产 Go 代码可用显式 kind 枚举加 string/list 字段,避免 interface{} 非检查式断言。
- list_push 在列表头插入;数组实现为 O(L) 搬移,若频繁头插可选双向链表,但按值从后删除又涉及遍历,需根据访问模式选择。
- list_remove count>0 从头删前 N 个匹配项;count<0 从尾删后 |N| 个;count=0 删除所有匹配项。返回删除数,保留剩余元素原相对顺序。
- 对同键 string/list 类型冲突先定义错误语义;千万不要把字符串自动转换为单元素列表而未说明。过期功能保留为未来扩展,数据结构预留类型与 metadata 位置即可。
手动走一遍
list_push(k,a)、list_push(k,b)、list_push(k,a) 后列表为 [a,b,a]。list_remove(k,a,1) 得 [b,a];从原列表执行 count=-1 则得 [a,b];count=0 则得 [b]。set(k,x) 后 get(k) 为字符串 x,再 list_push(k,y) 按训练约定报类型错误。
Python 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
"""Training reference for the posted Redis-like operations; no TTL."""
class MiniRedis:
def __init__(self):
self.values: dict[str, str | list[str]] = {}
def set(self, key: str, value: str) -> None:
self.values[key] = value
def list_push(self, key: str, value: str) -> None:
existing = self.values.get(key)
if existing is None:
self.values[key] = [value]
elif isinstance(existing, list):
existing.insert(0, value)
else:
raise TypeError("key holds a string")
def get(self, key: str) -> str | list[str] | None:
existing = self.values.get(key)
return list(existing) if isinstance(existing, list) else existing
def list_remove(self, key: str, value: str, count: int) -> int:
existing = self.values.get(key)
if existing is None:
return 0
if not isinstance(existing, list):
raise TypeError("key holds a string")
if count >= 0:
remaining = float("inf") if count == 0 else count
result = []
removed = 0
for item in existing:
if item == value and removed < remaining:
removed += 1
else:
result.append(item)
else:
remaining = -count
result = []
removed = 0
for item in reversed(existing):
if item == value and removed < remaining:
removed += 1
else:
result.append(item)
result.reverse()
self.values[key] = result
return removed
复杂度
键查找按通常哈希模型期望 O(1);set/get 字符串 O(1),get 列表副本 O(L),数组头插 O(L),list_remove 扫描 O(L) 时间并生成 O(L) 新列表;总存储 O(所有值长度)。若 Go 用链表,头插可 O(1),删除仍需按值扫描。
易错点与追问
容易失分的边界
- 把 count<0 理解成“删除负数个”或仍从头删除。
- get 直接泄漏可变列表,让外部调用破坏状态。
- 擅自实现 TTL 并声称来自面试题面;题目标明 deferred。
- 忽略类型冲突,导致同键 string 与 list 被静默混用。
继续深挖
- 如果现在加入 TTL,读取过期键与后台清理如何协作?
- 若 list_remove 在百万元素列表上很频繁,数组和链表各有什么真实缓存与内存开销?
- 并发 set/list_push 如何保证同键原子性?
