Level 1 Grants, burns and balances
Build GPUCredit with three methods:
add_credit(credit_id, amount, timestamp, expiration)creates a grant ofamountcredits.expirationis a duration: the grant can be used at every timetwithtimestamp <= t <= timestamp + expiration, and both ends are inclusive.credit_idvalues are unique, and nothing in this problem depends on them.subtract(amount, timestamp)burnsamountcredits attimestamp. Take them from the grants that are usable at that moment, starting with the one that expires soonest and moving on to the next one untilamountis covered. A subtract never fails. If the usable grants can't cover it, whatever's left over becomes debt.get_balance(timestamp) -> int | Nonereturns the remaining credits over all grants usable attimestamp, minus the debt.- It returns
Noneif no grant is usable attimestamp. A grant that's fully used up but hasn't expired yet still counts as usable. - It returns
Noneif the result is negative. A balance of exactly0is0, notNone.
- It returns
Debt: a new grant first pays off any outstanding debt. So after subtract(100) against a 10-credit grant, the debt is 90, and a later 50-credit grant is swallowed whole, leaving a debt of 40.
In this level, calls arrive in time order: every call's timestamp is >= the one before. When several calls share a timestamp they happen in call order.
g = GPUCredit()
g.add_credit("a", 4, 20, 40) # usable 20..60
g.add_credit("b", 3, 30, 10) # usable 30..40 (expires first)
g.subtract(2, 30) # taken from b: b=1, a=4
g.get_balance(40) # 5
g.get_balance(41) # 4 (b is gone)
g.get_balance(61) # None (no usable grant)
Queries and events can number in the tens of thousands, with a query after every event, so re-summing all the grants on every call is too slow. Aim for amortized O(log n) per call.
Show hint
a subtract always wants the usable grant that expires soonest, and expired grants can be thrown away the moment they're noticed rather than when they expire. Keep a running total alongside so a balance query doesn't have to add anything up.