← 返回博客列表 Ramp OA 复盘:CodeSignal 银行系统四个 Level
Ramp

Ramp OA 复盘:CodeSignal 银行系统四个 Level

2026-08-08

最近做了 Ramp 的 OA,题目跑在 CodeSignal 上,90 分钟四道题,分成四个 Level 逐级解锁。它不是那种一眼看穿套路的算法谜题,而是一道层层加码的系统设计实现题:你要把一个银行系统从最简单的存取转账,一步步扩展成支持排行、定时付款和取消的完整服务。这篇复盘记录整个流程、每个 Level 的要求和我的解法思路。如果你也在准备类似的 OA,希望这份笔记能帮你把节奏和数据模型提前想清楚。需要 OA辅助 或 OA代面的同学,也可以直接看文末的联系方式。

OA 环境与形式概览

值得先说清楚的一点:这套 90 分钟的 CodeSignal 银行系统格式取自一个共享题库,同样的风格在 Meta、Coinbase、TradeDesk 的 OA 里都出现过,目前市面上主要流传着三个大背景(场景),银行系统只是其中之一。所以提前熟悉这套「逐级构建」的出题方式,比临场硬啃更划算。

项目 详情
平台 CodeSignal
总时长 90 分钟
题量 4 道,对应 4 个 Level
形式 逐级解锁,必须当前 Level 全部通过才能进入下一级
考点 数据结构建模、增量扩展、按时间戳有序处理、堆与哈希表

最关键的规则:你必须把当前 Level 全部测试点跑对,下一个 Level 才会解锁。所以第一级一定要稳,别为了赶进度留下隐藏 bug——一旦某个 Level 卡住,后面的分数就全部锁死了。另外,每个操作都带一个唯一且严格递增的时间戳,这是后面几个 Level 排序和调度的基础。


Level 1:简易银行系统

题目背景

系统初始没有任何账户。需要支持三个操作:

思路

用一个 HashMap 存 accountId → 余额 即可,三个操作全是 O(1)。这一级时间戳只用来给操作排序,不影响逻辑。

Python 解法

from typing import Optional


class BankingSystem:
    def __init__(self) -> None:
        # accountId -> 余额
        self.accounts: dict[str, int] = {}

    def create_account(self, timestamp: int, account_id: str) -> bool:
        """新建账户;已存在则返回 False。"""
        if account_id in self.accounts:
            return False
        self.accounts[account_id] = 0
        return True

    def deposit(self, timestamp: int, account_id: str,
                amount: int) -> Optional[int]:
        """存款并返回最新余额;账户不存在返回 None。"""
        if account_id not in self.accounts:
            return None
        self.accounts[account_id] += amount
        return self.accounts[account_id]

    def transfer(self, timestamp: int, source_id: str, target_id: str,
                 amount: int) -> Optional[int]:
        """转账成功返回转出方剩余余额;否则返回 None。"""
        if source_id not in self.accounts or target_id not in self.accounts:
            return None
        if source_id == target_id:            # 不能转给自己
            return None
        if self.accounts[source_id] < amount:  # 余额不足
            return None
        self.accounts[source_id] -= amount
        self.accounts[target_id] += amount
        return self.accounts[source_id]

时间复杂度:createAccount / deposit / transfer 均为 O(1)。 空间复杂度:O(k),k 为账户数量。


Level 2:Top Spenders 排行

题目背景

新增 topSpenders(timestamp, n),返回累计转出金额最高的前 n 个账户。转出金额包括成功的转出转账,以及后面 Level 里成功执行的定时付款 / 取款。按转出金额降序排列,金额相同则按 accountId 升序,格式化为 "accountId(totalOutgoing)";若账户总数不足 n,则返回全部。

思路

给每个账户维护一个 outgoing 字段(创建时初始化为 0),只在转出方成功转账时累加它——存款和收到的转入都不计入。topSpenders 遍历所有账户排序即可。

Python 解法

class BankingSystem:
    def __init__(self) -> None:
        self.accounts: dict[str, int] = {}
        self.outgoing: dict[str, int] = {}   # accountId -> 累计转出

    def create_account(self, timestamp: int, account_id: str) -> bool:
        if account_id in self.accounts:
            return False
        self.accounts[account_id] = 0
        self.outgoing[account_id] = 0         # 转出额初始化为 0
        return True

    def transfer(self, timestamp: int, source_id: str, target_id: str,
                 amount: int) -> Optional[int]:
        if source_id not in self.accounts or target_id not in self.accounts:
            return None
        if source_id == target_id or self.accounts[source_id] < amount:
            return None
        self.accounts[source_id] -= amount
        self.accounts[target_id] += amount
        self.outgoing[source_id] += amount    # 只累加转出方
        return self.accounts[source_id]

    def top_spenders(self, timestamp: int, n: int) -> list[str]:
        """返回累计转出最高的前 n 个账户。"""
        ranked = sorted(
            self.accounts.keys(),
            key=lambda acc: (-self.outgoing[acc], acc),  # 转出降序,id 升序
        )
        return [f"{acc}({self.outgoing[acc]})" for acc in ranked[:n]]

时间复杂度:topSpenders 单次查询 O(m log m),m 为账户数;其余操作仍是 O(1)。 空间复杂度:O(m)。


Level 3:定时付款与取消

题目背景

新增两个操作:

两个关键时序规则:到期的定时付款必须先于该时间戳上的任何其他操作执行;多笔在同一时刻到期的,按创建顺序执行。

思路

用一个以 (executionTime, creationOrder) 为键的最小堆,配合一个 paymentId → 详情/状态 的 HashMap。在执行任何公开操作之前,先处理所有 executionTime <= 当前时间戳 的付款:跳过已取消的;余额够就扣款并累加转出,不够就标记失败。创建付款时生成递增 ID 并同时写入堆和 map;取消时按 ID 定位,校验归属和状态。

Python 解法

import heapq


class BankingSystem:
    def __init__(self) -> None:
        self.accounts: dict[str, int] = {}
        self.outgoing: dict[str, int] = {}
        self.pay_heap: list[tuple[int, int, str]] = []  # (执行时间, 顺序, id)
        self.payments: dict[str, dict] = {}   # paymentId -> 详情
        self.payment_counter = 0              # 全局递增 paymentId
        self.order_counter = 0                # 同刻到期的创建顺序

    def _process_due(self, timestamp: int) -> None:
        """执行所有 executionTime <= timestamp 的到期付款。"""
        while self.pay_heap and self.pay_heap[0][0] <= timestamp:
            _, _, payment_id = heapq.heappop(self.pay_heap)
            info = self.payments[payment_id]
            if info["status"] != "pending":
                continue                       # 已取消,跳过
            acc, amount = info["account_id"], info["amount"]
            if self.accounts.get(acc, 0) >= amount:
                self.accounts[acc] -= amount
                self.outgoing[acc] += amount   # 成功付款计入转出
                info["status"] = "done"
            else:
                info["status"] = "failed"      # 余额不足,跳过

    def schedule_payment(self, timestamp: int, account_id: str,
                         amount: int, delay: int) -> Optional[str]:
        self._process_due(timestamp)
        if account_id not in self.accounts:
            return None
        self.payment_counter += 1
        payment_id = f"payment{self.payment_counter}"
        self.order_counter += 1
        self.payments[payment_id] = {
            "account_id": account_id,
            "amount": amount,
            "status": "pending",
        }
        heapq.heappush(
            self.pay_heap,
            (timestamp + delay, self.order_counter, payment_id),
        )
        return payment_id

    def cancel_payment(self, timestamp: int, account_id: str,
                       payment_id: str) -> bool:
        self._process_due(timestamp)
        info = self.payments.get(payment_id)
        if info is None or info["account_id"] != account_id:
            return False
        if info["status"] != "pending":        # 已执行或已取消
            return False
        info["status"] = "cancelled"
        return True

时间复杂度:堆的插入 / 弹出 O(log n),n 为待执行付款数;取消按 ID 查表约 O(1)。每个公开操作前的 _process_due 总体摊还 O(log n)。 空间复杂度:O(n),用于堆和付款表。


Level 4:常见变体与应对策略

我这次抽到的 OA,Level 4 只给了标题就没能细看题面,所以这里不编造具体题目和代码,只诚实地说说方向。这套 CodeSignal 银行系统的 Level 4 通常是在前三级基础上继续扩展,常见的 Level 4 变体有:余额历史查询(回溯某账户在某时间戳的余额)、带过期规则的商户返现(cashback),以及账户合并(把一个账户的余额、转出记录和待执行付款并入另一个)。

应对未知的 Level 4,通用策略是:复用 Level 1-3 的数据模型(账户表、转出字段、付款堆),保持「按时间戳有序处理、公开操作前先结算到期事件」这条主线,然后仔细读题把新规则接到现有结构上。只要前三级的抽象搭得干净,第四级往往是自然延伸而非推倒重来。


备考建议

因为这套题来自共享题库,提前吃透题型比刷海量新题更有效


FAQ

Q1:Ramp 的 OA 一定要每个 Level 全对才能继续吗?

是的。CodeSignal 这套格式是逐级解锁,当前 Level 的全部测试点跑对后下一级才开放。所以宁可在低级别多花几分钟确认边界,也别留隐藏 bug 把后面的分数锁死。

Q2:90 分钟四道题,时间够用吗?

够,但前提是数据模型一开始就搭对。四道题是同一个系统的层层扩展,如果 Level 1 的抽象合理,后面基本是加字段和加方法;反之若前期结构混乱,到 Level 3 的堆和调度就会很吃力。

Q3:topSpenders 里的转出金额到底算哪些?

只算转出方的成功转账,加上后面成功执行的定时付款 / 取款。存款和收到的转入都不计入。相同金额按 accountId 升序排列,输出格式是 accountId(totalOutgoing)

Q4:定时付款的执行时序为什么这么重要?

因为到期付款必须先于同一时间戳上的其他操作执行,多笔同刻到期的还要按创建顺序处理。用 (executionTime, creationOrder) 的最小堆天然满足这个顺序,并在每个公开操作入口先调用一次结算,就不会漏。

Q5:这套银行系统题在别的公司也会遇到吗?

会。这套 90 分钟 CodeSignal 格式取自共享题库,Meta、Coinbase、TradeDesk 都出现过类似风格,目前主要流传三个大背景。把出题套路提前过一遍,跨公司都能复用。


正在准备 Ramp 或其他公司的 CodeSignal OA? 我们熟悉这套 90 分钟逐级解锁的银行系统格式,从数据建模到定时付款调度都能帮你梳理清楚,提供全流程的 OA辅助 与 OA代面服务。

立即添加微信 Coding0201获取一对一定制备考方案

联系方式