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 分(L1=20、L2=25、L3=25、L4=30) |
CodeSignal 的特殊之處:每個 Level 只暴露當級測試,下一級要等通過當前級才能解鎖。Level 1 如果把欄位型別寫死,到 Level 3 會被逼著大改。
Level 1:基礎 Key-Value 操作
題目描述
實作記憶體資料庫,支援:
SET <key> <field> <value>:寫入 recordGET <key> <field>:命中回傳值,否則回傳空字串DELETE <key> <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)
Level 2:掃描與前綴查詢
題目描述
新增:
SCAN <key>:回傳該 key 下所有field(value),依 field 字典序,逗號分隔SCAN_BY_PREFIX <key> <prefix>:僅回傳前綴相符項
解題思路
直接 sorted(items())。不要用 SortedDict——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)
Level 3:TTL 過期機制
題目描述
每個命令都帶時間戳:
SET_AT <key> <field> <value> <ts>:永久寫入SET_AT_WITH_TTL <key> <field> <value> <ts> <ttl>:在ts + ttl時刻過期DELETE_AT/GET_AT/SCAN_AT/SCAN_BY_PREFIX_AT:跳過已過期
判定嚴格:ts >= expire_at 即過期。
解題思路
把 value 改成 (value, expire_at),None 代表永久。所有讀取路徑都過同一個 helper _alive(key, field, ts),避免在 6 個方法裡散落判斷。
Python 解法
class InMemoryDB:
def __init__(self):
self.db = {}
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)
陷阱:很多人主動刪除已過期項——這會讓 Level 4 的「指定時間點還原」遺失資料。**延遲過期(lazy expire)**才是正確做法。
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 - 還原時改寫 expire_at:
new_expire = ts + remaining_ttl
很多人栽在第 2 步——直接拷回舊 expire_at,結果還原後 TTL 立刻過期。
Python 解法
class InMemoryDB:
def __init__(self):
self.db = {}
self.backups = {}
# ... 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 ""
Level 4 常見錯誤
- 用
copy.deepcopy(self.db)當快照——會把已過期資料帶回 - 還原時沒重算
expire_at——TTL 全部失效 - 讓
SET_AT反向動到 backup——backup 必須唯讀 - 過濾過期 + 計算剩餘做兩遍——合併成單次遍歷
Persona 備考策略
| 階段 | 推薦練習 |
|---|---|
| 熟悉 CodeSignal | LC 146 LRU Cache、LC 460 LFU Cache |
| 狀態機 | LC 432、LC 1206 Skiplist |
| TTL 思維 | Redis EXPIRE 文件 + LC 362 Hit Counter |
| 備份還原 | git 增量快照思路、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 考的是漸進式重構能力——四個 Level 累計 90 分鐘要相容無縫銜接。Level 1 寫死型別,Level 3 會很痛苦。
Q2:Persona OA 用什麼平台?多長時間?
CodeSignal General Coding Framework,90 分鐘。注意:之前還會有一個 60 分鐘的 GCA 預篩,那才是純 LeetCode 題型,In-Memory Database 是 GCA 之後的 Practical Task。
Q3:Backend 和 Full-stack 的 OA 一樣嗎?
OA 完全一致。Full-stack 在 OA 後多一輪 React/TypeScript 實戰,後端進入系統設計。OA 階段不必區分。
Q4:哪種語言通過率最高?
Python > Go > Java > JS。CodeSignal 的 Python 範本和巢狀 dict 寫法最省時間。避免用 Java,泛型語法會吃掉大量時間。
Q5:Level 4 的 backup_ts 如果不存在怎麼辦?
題目保證 backup_ts 之前 BACKUP 過——但多次 BACKUP 後 RESTORE 較早的那個是常見隱藏 case,要用 dict 不要用 list。
Q6:OA 通過後多久收到下一輪通知?
一畝三分地多數反饋 3-7 個工作日。Persona 流程節奏快,hiring manager 經常直接寄行事曆邀請,注意檢查垃圾信件夾。
正在準備 Persona / Ramp / Square 的 CodeSignal 系列 OA?
CodeSignal 四級題考的是工程化思維,不是演算法天花板。若想取得我們整理的同類公司題庫(Persona、Ramp、Stripe、Square、Brex),或希望針對 Level 3-4 狀態機做 1v1 複盤,歡迎聯繫。
加微信 Coding0201,取得 CodeSignal 四級題完整題庫。
聯絡方式
Email: [email protected] Telegram: @OAVOProxy