Persona Identities 作为身份验证赛道的明星 Y Combinator 公司,2026 年放出大量 Backend / Full-stack 岗位。它的 OA 不走 LeetCode 路线,而是走 CodeSignal 的"四级渐进题"——一个题面随 Level 加码,前一关的代码必须能兼容后一关。最常见的就是这道**"In-Memory Database"**,几乎已经成为 Persona OA 的"标志题",类似 Ramp、Square 也都采用过类似框架。
本篇基于一亩三分地最近的 Persona OA 复盘,把 Level 1-4 完整拆开,给出可以一路递增重构的 Python 实现,并讲一遍每个 Level 容易"挂掉"的坑。
Persona OA 概览
| 维度 | 详情 |
|---|---|
| 平台 | CodeSignal General Coding Framework |
| 时长 | 90 分钟(含 IDE 熟悉时间) |
| 题量 | 1 道大题 × 4 个 Level |
| 测试用例 | 累计约 25-30 个 |
| 语言 | Python / Java / Go / JS 均可,但 Python 通过率最高 |
| 满分 | 100 分(Level 1=20,Level 2=25,Level 3=25,Level 4=30) |
CodeSignal 的特殊之处:每个 Level 只暴露当级测试,下一级要等通过当前级才能解锁。这意味着 Level 1 的实现如果耦合写死、不抽接口,到 Level 3 会被逼着大规模重写。
Level 1:基础 Key-Value 操作
题目描述
实现内存数据库支持以下命令:
SET <key> <field> <value>:写入一个 record(key 下挂多个 field)GET <key> <field>:读取,命中返回值,否则返回空字符串DELETE <key> <field>:删除指定 field;删除成功返回"true",否则"false"
返回值必须是字符串。
解题思路
天然两层 dict:db[key][field] = value。重点是处理"key 不存在"的情况,不要预先创建空 dict——否则 Level 4 会污染备份。
Python 解法
class InMemoryDB:
def __init__(self):
self.db = {}
def set(self, key, field, value):
self.db.setdefault(key, {})[field] = value
return ""
def get(self, key, field):
return self.db.get(key, {}).get(field, "")
def delete(self, key, field):
if key in self.db and field in self.db[key]:
del self.db[key][field]
if not self.db[key]:
del self.db[key]
return "true"
return "false"
时间复杂度:所有操作均摊 O(1) 空间复杂度:O(K·F),K=key 数,F=平均 field 数
Level 2:扫描与前缀查询
题目描述
新增两条命令:
SCAN <key>:返回该 key 下所有field(value)字符串,按 field 字典序拼接,逗号分隔SCAN_BY_PREFIX <key> <prefix>:仅返回前缀匹配的项
解题思路
直接 sorted(self.db[key].items()) 排序就够。不要存有序结构(如 SortedDict)——Persona 的 Level 4 会做批量回滚,越简单越好维护。
Python 解法
def scan(self, key):
if key not in self.db:
return ""
items = sorted(self.db[key].items())
return ", ".join(f"{f}({v})" for f, v in items)
def scan_by_prefix(self, key, prefix):
if key not in self.db:
return ""
items = sorted(
(f, v) for f, v in self.db[key].items() if f.startswith(prefix)
)
return ", ".join(f"{f}({v})" for f, v in items)
时间复杂度:O(F log F),F = 该 key 下 field 数 空间复杂度:O(F)
Level 3:TTL 过期机制
题目描述
引入"时间戳"语义——每次命令都带一个时间戳参数:
SET_AT <key> <field> <value> <ts>:永久写入SET_AT_WITH_TTL <key> <field> <value> <ts> <ttl>:在ts + ttl时刻过期DELETE_AT <key> <field> <ts>:仅当未过期才能删除GET_AT <key> <field> <ts>:考虑过期SCAN_AT/SCAN_BY_PREFIX_AT:跳过过期项
过期判定严格 ts ≥ expire,等于也算过期。
解题思路
把 value 从 string 改为 (value, expire_at) 元组,expire_at = None 表示永久。所有读取路径都过同一个 helper:_alive(key, field, ts),避免在 6 个方法里散落判断。
Python 解法
class InMemoryDB:
def __init__(self):
self.db = {} # key -> field -> (value, expire_at|None)
def _alive(self, key, field, ts):
rec = self.db.get(key, {}).get(field)
if rec is None:
return None
value, expire_at = rec
if expire_at is not None and ts >= expire_at:
return None
return value
def set_at(self, key, field, value, ts):
self.db.setdefault(key, {})[field] = (value, None)
return ""
def set_at_with_ttl(self, key, field, value, ts, ttl):
self.db.setdefault(key, {})[field] = (value, ts + ttl)
return ""
def get_at(self, key, field, ts):
v = self._alive(key, field, ts)
return "" if v is None else v
def delete_at(self, key, field, ts):
if self._alive(key, field, ts) is None:
return "false"
del self.db[key][field]
return "true"
def scan_at(self, key, ts):
if key not in self.db:
return ""
live = sorted(
(f, v) for f, (v, exp) in self.db[key].items()
if exp is None or ts < exp
)
return ", ".join(f"{f}({v})" for f, v in live)
时间复杂度:读写均摊 O(1),扫描 O(F log F) 陷阱:很多人把过期项立即从 dict 中删除——这会让 Level 4 的"指定时间点恢复"丢数据。惰性过期才是正确做法。
Level 4:备份与恢复(Backup / Restore)
题目描述
新增:
BACKUP <ts>:在时刻 ts 做一次快照,返回当前未过期 key 的数量作为字符串RESTORE <ts> <backup_ts>:把状态恢复到backup_ts的快照,但所有 TTL 要按现在时间 ts 重新计算剩余时长
解题思路
两个关键点:
- 快照存什么:不能存浅拷贝。要存
(field, value, remaining_ttl),其中remaining_ttl = expire_at - backup_ts,永久项存None - 恢复时怎么重写 expire_at:
new_expire = ts + remaining_ttl
很多人栽在第 2 步——直接把旧 expire_at 拷回来,结果恢复后所有 TTL 都"瞬间过期"。
Python 解法
import copy
class InMemoryDB:
def __init__(self):
self.db = {}
self.backups = {} # backup_ts -> snapshot
# ... 省略 Level 1-3 方法,沿用上面 ...
def backup(self, ts):
snapshot = {}
for key, fields in self.db.items():
live = {}
for f, (v, exp) in fields.items():
if exp is None:
live[f] = (v, None)
elif ts < exp:
live[f] = (v, exp - ts) # 存"剩余时长"
if live:
snapshot[key] = live
self.backups[ts] = snapshot
return str(len(snapshot))
def restore(self, ts, backup_ts):
if backup_ts not in self.backups:
return ""
snap = self.backups[backup_ts]
new_db = {}
for key, fields in snap.items():
new_fields = {}
for f, (v, remaining) in fields.items():
exp = None if remaining is None else ts + remaining
new_fields[f] = (v, exp)
new_db[key] = new_fields
self.db = new_db
return ""
时间复杂度:BACKUP O(K·F),RESTORE O(K·F) 空间复杂度:O(B·K·F),B = 备份次数
Level 4 易错点清单
- ❌ 直接
copy.deepcopy(self.db)当快照——会把已过期数据带回来 - ❌ 恢复时不重置 expire_at——TTL 全部失效
- ❌ 把 SET_AT 写到 backup 里——backup 是"只读快照",不允许逆向影响 db
- ✅ 把"过滤过期 + 计算剩余 TTL"合并到一次遍历,节省一倍内存
Persona 备考策略
| 阶段 | 推荐练习 |
|---|---|
| 熟悉 CodeSignal | LC 146 LRU Cache、LC 460 LFU Cache |
| 状态机 | LC 432 All O`one Data Structure、LC 1206 Skiplist |
| TTL 思维 | Redis EXPIRE 文档 + LC 362 Hit Counter |
| 备份恢复 | git-style 增量快照思路、LC 1166 File System |
面试节奏建议:Level 1 控制在 15 分钟内拿满,Level 2-3 各 20-25 分钟,Level 4 留 25-30 分钟。千万别 Level 1 就开始写"未来扩展性极佳的抽象类"——CodeSignal 不会因为代码"优雅"加分,只看测试通过率。
FAQ
Q1:Persona OA 难度怎么样?比 LeetCode Medium 难吗?
不能直接对比。单题难度不超过 LC Medium,但 Persona OA 考的是渐进式重构能力——4 个 Level 累计 90 分钟内不仅要写对,还要让前一级代码"无缝兼容"下一级。如果 Level 1 把 value 写死成字符串,Level 3 会非常痛苦。
Q2:Persona OA 用什么平台?时长多长?
CodeSignal General Coding Framework,90 分钟。注意:考前还会有一个 60 分钟的 General Coding Assessment(GCA)作为筛选,那个才是纯 LeetCode 题型。In-Memory Database 是 GCA 之后的 Practical Task。
Q3:Persona 后端岗和 Full-stack 岗的 OA 一样吗?
题目本身完全一样。只是 Full-stack 岗在 OA 通过后会加一轮 React/TypeScript 实战,后端则进入系统设计。OA 阶段不需要区分准备。
Q4:用什么语言通过率最高?
Python > Go > Java > JS。CodeSignal 的 Python 模板自带类型提示和 IDE 自动补全,写四级嵌套字典最快。Java 慎用——Map 嵌套泛型语法噪声会大量挤占 90 分钟时间。
Q5:Level 4 的 backup_ts 不存在怎么办?
题目原文里 RESTORE 给定 backup_ts 一定是之前 BACKUP 过的——但多个 backup 后再 RESTORE 较早的那一个是常见隐藏 case。建议用 dict 而不是 list 存 backups,按 ts 索引。
Q6:OA 通过后多久收到下一轮通知?
一亩三分地多数反馈是 3-7 个工作日。如果 7 天没消息建议主动跟进 recruiter——Persona 流程节奏快,hiring manager 经常直接发 calendar 邀请,邮件容易漏看。
正在准备 Persona / Ramp / Square 的 CodeSignal 系列 OA?
CodeSignal 的"四级题"考的不是算法天花板,而是工程化思维和状态管理。如果你想拿到我们整理的同类公司四级题题库(Persona、Ramp、Stripe、Square、Brex),或希望针对 Level 3-4 的状态机做 1v1 复盘,欢迎联系。
立即添加微信 Coding0201,获取 CodeSignal 四级题完整题库。
联系方式
Email: [email protected] Telegram: @OAVOProxy