OCI SWE · Technical Screening Coding

Oracle OCI 简化 Redis:字符串、头插列表与按 Count 删除

按 OCI 题目列出的 set、list_push、get 与 list_remove 语义构建类型化存储,明确正负 count 方向和 TTL 暂缓。

题目重点:按 OCI 题目列出的 set、list_push、get 与 list_remove 语义构建类型化存储,明确正负 count 方向和 TTL 暂缓。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 原题要求 Golang;下方 Python 是独立可运行的语义对照,接口逻辑可迁移到 Go 的 tagged union 或带类型结构,不声称是题目语言实现。
  • set 可覆盖任意原值;list_push 遇字符串键报类型错误,缺失键创建列表;list_remove 缺失键返回 0,字符串键报类型错误。题目未明确错误行为,这些是训练约定。
  • get 返回列表副本,防止调用者绕过 API 修改存储;不实现 TTL,因为题目明确标注 deferred。

解题路线

  1. map 的 value 用字符串或列表两种类型表示;生产 Go 代码可用显式 kind 枚举加 string/list 字段,避免 interface{} 非检查式断言。
  2. list_push 在列表头插入;数组实现为 O(L) 搬移,若频繁头插可选双向链表,但按值从后删除又涉及遍历,需根据访问模式选择。
  3. list_remove count>0 从头删前 N 个匹配项;count<0 从尾删后 |N| 个;count=0 删除所有匹配项。返回删除数,保留剩余元素原相对顺序。
  4. 对同键 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 如何保证同键原子性?
✓已复制微信号!