Design a Bank class that manages n bank accounts and supports three operations:
transfer(account1, account2, money)— if both accounts exist andaccount1has at leastmoney, movemoneyfromaccount1toaccount2, returntrue; otherwise returnfalse.deposit(account, money)— if account exists, addmoneyto the account and returntrue; otherwise returnfalse.withdraw(account, money)— if account exists and has at leastmoney, subtractmoneyand returntrue; otherwise returnfalse.
Accounts are 1-indexed (account numbers 1..n). Balances are large integers (use long long).
This is a straightforward stateful data-structure problem: store balances in a container (vector) indexed by account id minus one. Each operation is just input validation + small arithmetic on two entries. Because n can be large but operations are O(1), the overall performance is excellent for typical usage.
Key things to watch for:
- Indexing: user-facing accounts are 1-indexed while
vectoris 0-indexed. - Validation: must check account bounds and sufficient funds for withdraw/transfer.
- Integer overflow: adding money might overflow
long longif inputs are adversarial; decide whether to guard or rely on constraints. - Robustness: reject negative money (if domain forbids it). Protect against misuse.
- Thread-safety: class is not thread-safe; add locking if concurrency is required.
- Brute force doesn't apply here — operations are constant-time by definition.
- Optimal is simply O(1) per operation using direct index access. That's what the provided design already does.
class Bank {
public:
int n;
vector<long long> bal;
bool validAc(int acc) {
if (acc > 0 && acc <= n)
return true;
else return false;
}
Bank(vector<long long>& balance) {
bal = balance;
n = balance.size();
}
bool transfer(int account1, int account2, long long money) {
if (!validAc(account1) || !validAc(account2) || bal[account1-1] < money)
return false;
bal[account1-1] -= money;
bal[account2-1] += money;
return true;
}
bool deposit(int account, long long money) {
if (!validAc(account))
return false;
bal[account-1] += money;
return true;
}
bool withdraw(int account, long long money) {
if (!validAc(account) || bal[account-1] < money)
return false;
bal[account-1] -= money;
return true;
}
};- Time (per operation):
O(1)— constant-time checks and index updates. - Space:
O(n)to store balances. - Initialization:
O(n)to copy the balances into the vector (orO(1)if youmovein the constructor).
This is optimal for the required functionality.
Assume accounts [10, 100, 20, 50, 30] (accounts 1..5).
-
Valid transfer
transfer(1, 2, 5)→ true Balances →[5, 105, 20, 50, 30]
-
Insufficient funds
transfer(1, 3, 10)→ false (account1 has only 5 now)
-
Withdraw valid
withdraw(2, 100)→ true → balances[5, 5, 20, 50, 30]
-
Withdraw insufficient
withdraw(3, 30)→ false
-
Deposit
deposit(4, 1000)→ true → balances[5, 5, 20, 1050, 30]
-
Invalid account
transfer(0, 2, 1)ordeposit(6, 10)→ false
-
Negative money
deposit(1, -10)→ false (rejected)
-
Overflow guard (if enabled)
- set account 5 to near
LLONG_MAX, tryingdeposit(5, 1000)should return false or be handled.
- set account 5 to near
- 1-indexing — remember to subtract
1for vector access. - Negative amounts — invalid input usually; decide policy and enforce.
- Overflow — if inputs may be near
LLONG_MAX, consider safe add or use__int128internally. - Concurrency — simultaneous operations on accounts can lead to race conditions. Use locks if necessary.
- Large sizes —
ncan be large; ensure memory fits. - Signed/unsigned mixing — casting to
size_thelps avoid warnings, but be consistent.
- Thread-safe Bank: add
std::mutexper account or one global mutex. Per-account locks improve concurrency for operations on different accounts. - Transaction logging: store a history of operations for auditing and rollback.
- Atomic transfers: ensure transfer is atomic (deduct+add) under concurrency using locks or atomic math for simpler cases.
- Support currency & fees: add decimals with
long longcents or use arbitrary precision. - Fraud detection rules: block transfers above a threshold or to suspicious accounts.
- Use
const vector<long long>&in constructor to avoid extra copies; if caller won't needbalanceafterwards, provide an rvalue overload (Bank(vector<long long>&&)) to move. - Keep validation centralized in one helper (like
validAc) to avoid mistakes. - Prefer using
size_tfor index checks (acc >= 1 && acc <= n), but be careful with signedintparameters — cast explicitly. - Document the indexing scheme (1-based) and behavior with negative/oversized amounts.
- Write unit tests to validate boundary conditions: highest/lowest balances, invalid accounts, negative money, concurrency if applicable.
Q: Should I support negative money values?
A: Typically no. Banking operations deal with non-negative amounts. Reject negatives or interpret them explicitly (e.g., deposit(-x) == withdraw(x)) only if problem states so.
Q: Do I need to worry about overflow?
A: If input constraints guarantee balances and money fit in 64-bit signed (
long long), you can skip checks. For robustness use overflow guards.
Q: How to make this thread-safe?
A: Add
std::mutexfor each account and lock both accounts intransfer(use consistent locking order to avoid deadlock), or usestd::shared_mutexas appropriate.
Q: Is this implementation optimal?
A: Yes — all operations are
O(1). The bottleneck is memoryO(n), unavoidable if you must store every balance.
Q: Should balances be signed?
A:
long longis fine; if negative balance allowed (overdraft), allow negative values; otherwise reject operations that would produce negative balance.