最近 Airbnb SDE 发了挺多 OA,CodeSignal 的 Filesystem / 银行账户这类多关卡题写了不少,手感比较熟。这场 Airbnb OA 大约 40 分钟过完,压力不大。把四关的题意和思路都写清楚,有在备考的直接参考。

题型说明
典型 CodeSignal 多 Level 结构:在同一套银行账户系统上逐级加功能,前一关测例过了才能解锁下一关。时间戳严格递增,查询按给定 timestamp 处理。
Level 1:开户、存款、付款

题意
三个接口:createAccount 新建账户、deposit 存款、pay 取款。
createAccount:账户已存在返回 false,否则建户余额为 0 返回 true。
deposit:账户不存在返回 null,否则加钱并返回新余额。
pay:账户不存在或余额不足返回 null,否则扣钱返回新余额。
思路
用一个 HashMap 维护 accountId → 余额的映射。三个接口都是先判合法性再操作,返回值类型是关键——成功返回余额(字符串或整数,看题面),失败返回 null,两者不能搞混。
这关没有难点,重要的是把数据结构建对、返回值类型对齐,为后面几关打好基础。
Level 2:按交易总额排名

题意
接口 topActivity(timestamp, n):按各账户的累计交易额从高到低取前 n 个,同额时按 accountId 字典序升序,返回格式为 “accountId(value)” 的字符串列表。
思路
在 Level 1 的余额 map 基础上,再维护一个 accountId → 累计交易额 的 map。存款和成功取款都要计入,Level 3 的成功转账之后双方也要更新。
查询时把所有账户拉出来排序——主键是交易额降序,次键是 accountId 升序,取前 n 条拼字符串。账户数通常不大,直接排序即可,不需要维护有序结构。
最容易漏的坑:转账成功之后要同时更新转出方和转入方的累计交易额,不只是更新余额,漏了这步 Level 2 的排名就会算错。
Level 3:待确认转账

题意
两个新接口:transfer 和 acceptTransfer。
transfer(timestamp, sourceId, targetId, amount):从 source 账户转出 amount,先冻结金额(从余额扣除),生成一个唯一的 transferId 并返回;如果 24 小时内没有收到 acceptTransfer,视为过期,冻结金额退回 source 账户。
acceptTransfer(timestamp, accountId, transferId):收款方确认,金额正式入账到 target 账户,并记入双方的交易历史。
思路
需要一个 transfer 状态机,每笔转账有三种状态:pending(已冻结等待确认)、accepted(已完成)、expired(已过期退回)。
数据结构上,用一个 HashMap 维护 transferId → {sourceId, targetId, amount, expireTime, status}。
transfer 时:先校验 source 和 target 账户是否存在、source 余额是否足够,通过后从 source 余额扣除金额(冻结),记录转账信息,状态设为 pending。
acceptTransfer 时:先检查 transferId 是否存在、状态是否为 pending、当前时间是否在过期时间之前,全部通过才把金额加到 target 余额,状态改为 accepted,并更新双方的累计交易额。
过期处理:在后续任何操作(deposit、pay、transfer、acceptTransfer、topActivity)执行前,先扫一遍所有 pending 转账,把 expireTime ≤ 当前时间的状态改为 expired、金额退回 source 余额。也可以懒处理——只在涉及该账户时才检查。
边界要覆盖:source 和 target 是同一个账户返回 null;账户不存在返回 null;余额不足返回 null;只有 accepted 状态的转账才计入交易历史,pending 状态不算。
Level 4:合并账户 + 历史时刻余额

题意
两个新接口:mergeAccounts 和 getBalance。
mergeAccounts(timestamp, accountId1, accountId2):把 account2 并入 account1,余额相加、历史记录合并,account2 相关的未完成转账按题意取消或改指向,account2 账户删除。
getBalance(timestamp, accountId, timeAt):返回该账户在 timeAt 时刻的余额;如果该账户在 timeAt 时刻还不存在则返回 null。
思路
历史余额:从 Level 1 开始,每次余额发生变化时,就往该账户的历史数组里追加一条 (time, balanceAfter) 记录。时间戳严格递增,历史数组天然有序。
查询时,在该账户的历史数组上做二分搜索,找到最后一个 time ≤ timeAt 的记录,返回对应的 balanceAfter。如果找不到(timeAt 早于账户创建时间),返回 null。
合并:
一是余额直接相加,记录一条新的历史快照。
二是把 account2 的交易历史并入 account1,按时间戳排序后合并(合并前两者各自的历史都要保留,这样 getBalance 查合并前的时刻也能查到正确余额)。
三是处理 account2 相关的 pending 转账:account2 作为转出方的取消并退款,account2 作为转入方的改指向 account1。
四是从 accountId map 里删掉 account2。
注意合并后历史的组织方式——account1 的历史要能覆盖合并前两个账户各自的时间段,查 timeAt < 合并时间的余额时,应该只看对应账户在合并前的历史,而不是合并后的总余额。这块是 Level 4 里最绕的地方,提前在纸上画清楚状态再写。
几点体会
这类题不靠花哨算法,靠状态维护清楚、边界一次写对。四关里最核心的两块:Level 3 的状态机(三种状态之间的转移、过期退款的时机)和 Level 4 的历史快照(每次变更都要记录、合并后历史的查询逻辑)。
建议进去之前先在纸上把状态转换图画一遍,特别是 pending → expired 的退款触发时机,以及合并后 getBalance 的查询范围,这两块想清楚了代码逻辑自然就顺了。
CodeSignal Banking / Filesystem 多关卡最近在 Airbnb、Uber、Capital One 都有出现,写熟一套模板之后进去基本是复现。
有在准备 Airbnb 或其他 CodeSignal 公司 OA 的同学,可以来找我们聊聊。InterviewShow 整理了这套多关卡题型的完整模板和边界 checklist,按目标公司给针对性刷题清单,有需要的来聊。