题目重点:实现创建、存取、交易活跃度排名与限时接受转账,保证冻结资金只结算或退回一次。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 训练时间为严格递增整数毫秒,金额为正整数最小单位;Activity=成功deposit+pay,接受转账时双方各加amount。它不是余额,也不是仅支出排名。
- transfer创建即扣source可用余额,目标accept才到账。有效截止created+86,400,000,本训练在t等于截止时已过期;每个有时间操作前处理deadline≤t。截止是否含端点需按具体题面确认。
- merge与getBalance的存在可辨,但账户生命周期、被吸收账户查询、待接受转账迁移未给完整规则;示例实现止于前三阶段,下面解释第四阶段需要定的合同,不假称隐藏规则已确定。
解题路线
- 账户表分balances和activity,成功存取更新各自指标,失败操作不计Activity。TopN对所有账户按(-activity,id)排序,因此零交易账户也参与,n超过账户数返回全部。
- 转账记录(source,target,amount,deadline,status)与独立单调transferId。创建校验账户不同、双方存在与余额,然后冻结扣款、写pending和到期heap;创建本身不计Activity。
- accept先清理到期项,再核对target身份与pending状态。成功给目标入账、双方Activity增加、状态变accepted。重复accept和错误用户都失败,不重复入账。
- 过期清理只把pending变expired并给source退款。accepted记录仍可能留在heap,到时应跳过;用状态保证一次结算,不能删除heap项作为唯一正确性依据。
- 资金守恒在模型内为sum(availableBalances)+sum(pendingAmounts)=成功存入−成功取出。Activity不遵循同一守恒式,转账会给两侧都累计。用账本重算作为独立对照比重复调用同一方法更能发现指标混淆。
- 若追加merge,需要确定source/target被合并后accept权限、内部转账和截止退款去向;getBalance需要记录账户创建/删除边界与操作后余额。对历史查询维护有效时间状态,不能直接查询当前balance冒充timeAt。
手动走一遍
a存100、pay20后余额80、Activity120;向b转30后a余额50,pending30,Activity仍120。b接受后b余额30,a Activity150、b Activity30。若未接受到期,则a余额恢复80,Activity仍120,不能把退款再算一次deposit。
Python 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
import heapq
class AcceptBank:
"""Training: strictly increasing ms timestamps; expire at created+DAY.
Before every timed operation expire pending transfers with deadline<=time.
Activity includes successful deposit/pay and both sides of accepted transfer.
Merge/history policy is intentionally outside this executable contract.
"""
DAY=86_400_000
def __init__(self):
self.balances={};self.activity={};self.transfers={};self.expiry=[]
self.serial=0;self.last=float('-inf')
def _advance(self,t):
if t<=self.last:raise ValueError('timestamps must strictly increase')
self.last=t
while self.expiry and self.expiry[0][0]<=t:
_,serial=heapq.heappop(self.expiry)
item=self.transfers[serial]
if item[4]=='pending':
self.balances[item[0]]+=item[2];item[4]='expired'
def create(self,t,account):
self._advance(t)
if account in self.balances:return False
self.balances[account]=0;self.activity[account]=0;return True
def deposit(self,t,account,amount):
self._advance(t)
if amount<=0:raise ValueError('positive amount required')
if account not in self.balances:return None
self.balances[account]+=amount;self.activity[account]+=amount
return self.balances[account]
def pay(self,t,account,amount):
self._advance(t)
if amount<=0:raise ValueError('positive amount required')
if account not in self.balances or self.balances[account]<amount:return None
self.balances[account]-=amount;self.activity[account]+=amount
return self.balances[account]
def transfer(self,t,source,target,amount):
self._advance(t)
if amount<=0:raise ValueError('positive amount required')
if source==target or source not in self.balances or target not in self.balances:return None
if self.balances[source]<amount:return None
self.serial+=1;self.balances[source]-=amount
self.transfers[self.serial]=[source,target,amount,t+self.DAY,'pending']
heapq.heappush(self.expiry,(t+self.DAY,self.serial));return self.serial
def accept(self,t,account,serial):
self._advance(t);item=self.transfers.get(serial)
if item is None or item[1]!=account or item[4]!='pending':return False
source,target,amount,_,_=item
self.balances[target]+=amount
self.activity[source]+=amount;self.activity[target]+=amount;item[4]='accepted'
return True
def top(self,t,n):
self._advance(t)
if n<0:raise ValueError('negative count')
return sorted(self.activity.items(),key=lambda p:(-p[1],p[0]))[:n]
复杂度
A账户、T转账,普通存取均摊O(1)加本次到期清理;每条到期项入出heap O(log T),整体摊销,而不是每次请求无条件O(1)。排名基线O(A log A),状态与heap O(A+T)。保留终态记录利于重复请求识别,但需要回收政策。
易错点与追问
容易失分的边界
- 到期处理只放accept中,deposit/pay可能使用本该已退款的错误余额。
- Activity与余额复用一个变量,会让pay降低排名或创建transfer提前计分。
- 退款用deposit公共接口会错误增加Activity,且可能破坏严格递增时钟。
继续深挖
- 是否允许转账创建重试?接口可加入requestId返回同一transferId;当前练习每次调用视为新请求。
- 第四阶段getBalance(timeAt)应显示冻结后的可用余额还是含冻结的总额?先确认输出口径再设计历史。
