Portfolio Tickers with Constant-Time Random Pick
Problem statement
Build a portfolio of stock ticker symbols that supports three operations, each in constant time on average: add a ticker, remove a ticker, and pick a random ticker.
To make the random pick testable, the portfolio keeps its tickers in numbered slots 0, 1, ..., size - 1, and every pick comes with a number r that stands in for the random draw. Process the rows of operations in order:
["add", ticker]: iftickeris not in the portfolio, put it in a new slot at the end (slotsize). If it is already there, do nothing.["remove", ticker]: iftickeris in slotj, move the ticker from the last slot into slotjand then delete the last slot. Iftickerwas itself in the last slot, just delete that slot. Iftickeris not in the portfolio, do nothing.["pick", r]: return the ticker in slotr mod size.
Return the answers of all "pick" rows, in order.
If r is chosen uniformly from 0 to size - 1, every ticker is equally likely to be picked. The slot rules are exactly what a constant-time design does, so they also fix which ticker each r selects.
Follow-ups to discuss (not judged):
- Each ticker has a positive weight, and a pick should return a ticker with probability proportional to its weight. How do you support add, remove and pick?
- The tickers arrive as a stream that is too long to store, and you must return one ticker chosen uniformly from everything seen so far. How do you do it in constant memory?
Function
runPortfolio(operations: String[][]) → String[]Examples
Example 1
operations = [["add","AAPL"],["add","MSFT"],["add","GOOG"],["pick","4"],["remove","AAPL"],["pick","0"],["pick","1"],["add","NVDA"],["pick","5"]]return = ["MSFT","GOOG","MSFT","NVDA"]- After three adds the slots are
[AAPL, MSFT, GOOG].4 mod 3 = 1, so the first pick returns"MSFT". - Removing
AAPLfrom slot0moves the last ticker,GOOG, into slot0. The slots are now[GOOG, MSFT], so picks0and1return"GOOG"and"MSFT". - Adding
NVDAgives[GOOG, MSFT, NVDA], and5 mod 3 = 2returns"NVDA".
Example 2
operations = [["add","TSLA"],["add","TSLA"],["remove","AMZN"],["pick","1"],["add","AMZN"],["remove","TSLA"],["pick","7"]]return = ["TSLA","AMZN"]The second add of TSLA does nothing because it is already in the portfolio, and removing AMZN does nothing because it is not there yet. So the first pick sees one slot, and 1 mod 1 = 0 returns "TSLA". After AMZN is added and TSLA is removed, only AMZN is left.
Example 3
operations = [["add","A"],["add","B"],["add","C"],["remove","C"],["add","D"],["pick","2"],["remove","A"],["pick","0"],["pick","3"]]return = ["D","D","B"]Removing C, which is in the last slot, just drops that slot. D then takes slot 2, so pick 2 returns "D". Removing A moves D into slot 0, giving [D, B]; pick 0 returns "D" and 3 mod 2 = 1 returns "B".
Constraints
1 <= operations.length <= 2000- Each row has exactly
2entries. The first is"add","remove"or"pick". - For
"add"and"remove", the ticker has1to5uppercase English letters. - For
"pick",ris a decimal integer with no leading zeros and0 <= r <= 10^9. - Every
"pick"happens while the portfolio holds at least one ticker.