Distribute Money Fairly Under Recipient Caps
Company: Gusto
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Onsite
## Distribute Money Fairly Under Recipient Caps
### Problem
Implement `distribute(amount, recipients)`.
`amount` is a pool of nonnegative integer minor currency units. `recipients` is a JSON array of `[recipient_id, owed]` pairs. IDs are unique strings, and each `owed` value is a nonnegative integer.
### Function Contract
```text
distribute(amount, recipients) -> payments
```
`payments` is a JSON array of `[recipient_id, paid]` pairs.
Return one `[recipient_id, paid]` pair for every recipient in the original input order.
### Fairness Contract
Let `pool = min(amount, sum(owed))`. The returned payments must total `pool`, and no payment may exceed what that recipient is owed.
To make “as evenly as possible” unique, use capped water filling:
1. Find the greatest integer level `L` in the closed interval `0 <= L <= max(owed)` such that `sum(min(owed[i], L)) <= pool`.
2. Initially pay recipient `i` exactly `min(owed[i], L)`.
3. Let `R` be the remaining unallocated units. Give one additional unit to the first `R` recipients in input order whose `owed[i]` is greater than `L`.
This exhausts as much of the pool as possible, pays capped recipients no more than they are owed, and differs by at most one unit among recipients who remain above the water level.
When `pool == sum(owed)`, the bounded definition gives `L = max(owed)`, the remainder is zero, and every recipient is paid exactly what they are owed.
### Examples
```text
amount = 40
recipients = [["a", 10], ["b", 10], ["c", 10], ["d", 10]]
result = [["a", 10], ["b", 10], ["c", 10], ["d", 10]]
```
```text
amount = 10
recipients = [["a", 2], ["b", 8], ["c", 8]]
result = [["a", 2], ["b", 4], ["c", 4]]
```
```text
amount = 5
recipients = [["a", 10], ["b", 10], ["c", 10]]
result = [["a", 2], ["b", 2], ["c", 1]]
```
```text
amount = 20
recipients = [["a", 3], ["b", 4]]
result = [["a", 3], ["b", 4]]
```
### Requirements
- Support `1 <= recipients.length <= 200,000`.
- Support `0 <= amount <= 1,000,000,000,000`.
- Support `0 <= owed <= 10,000,000,000` for every recipient.
- Each ID contains `1` to `64` ASCII characters.
- Use exact integer arithmetic. All stated values and sums fit safely in signed 64-bit integers and JavaScript's exact-integer range.
- Target `O(n log n)` time or better and `O(n)` auxiliary space.
- Do not distribute one currency unit at a time; `amount` can be very large.
```hint Sort the caps, not the currency units
When the smallest debts are satisfied, remove them from the equal-sharing group and reason about the next possible common level.
```
### Discussion Prompts
1. Why does equal division followed by clipping fail to exhaust the pool in some cases?
2. How can sorting recipients by `owed` reveal the water level without iterating through every currency unit?
3. Why is the original input order needed after the level is found?
4. What happens when the pool exceeds the total amount owed?
Quick Answer: Implement a capped fair distribution of integer currency units while respecting each recipient's owed amount and original order. Formalize a unique fairness contract, find the allocation level efficiently, handle remainders deterministically, and preserve exact totals.
You are splitting a single pool of money among a list of recipients, each of whom is owed some amount. Implement `distribute(amount, recipients)`.
`amount` is a pool of nonnegative integer minor currency units (think cents). `recipients` is a list of `[recipient_id, owed]` pairs: `recipient_id` is a unique string and `owed` is a nonnegative integer.
Return a list of `[recipient_id, paid]` pairs -- exactly one pair per recipient, in the original input order.
### Fairness contract
Let `pool = min(amount, sum(owed))`. The payments you return must total exactly `pool`, and no recipient may be paid more than they are owed.
"As evenly as possible" is made unique by **capped water filling**:
1. Find the greatest integer level `L` with `0 <= L <= max(owed)` such that `sum(min(owed[i], L)) <= pool`.
2. Initially pay recipient `i` exactly `min(owed[i], L)`.
3. Let `R = pool - sum(min(owed[i], L))` be the units still unallocated. Give one additional unit to the **first `R` recipients, in input order, whose `owed[i]` is strictly greater than `L`**. A recipient with `owed[i] <= L` never receives an extra unit.
This exhausts as much of the pool as the caps allow, never overpays anyone, and leaves at most one unit of difference between any two recipients who are still below their cap.
When `pool == sum(owed)`, the bounded definition gives `L = max(owed)`, `R = 0`, and every recipient is paid in full.
### Output semantics
- One `[recipient_id, paid]` pair per input recipient -- same count, same order as the input. Never sort the result by `owed` or by id.
- `recipient_id` is returned unchanged; `paid` is a nonnegative integer.
- Steps 1-3 pin down a single payment vector, so every input has exactly one correct answer.
### Examples
```text
amount = 40
recipients = [["a", 10], ["b", 10], ["c", 10], ["d", 10]]
result = [["a", 10], ["b", 10], ["c", 10], ["d", 10]]
```
`sum(owed) = 40`, so `pool = 40 = sum(owed)`. `L = 10`, `R = 0`, and everyone is paid in full.
```text
amount = 10
recipients = [["a", 2], ["b", 8], ["c", 8]]
result = [["a", 2], ["b", 4], ["c", 4]]
```
`pool = 10`. `sum(min(owed, 4)) = 2 + 4 + 4 = 10 <= 10` while `sum(min(owed, 5)) = 12 > 10`, so `L = 4` and `R = 0`. Recipient `a` is capped at 2 and the other two split the rest.
```text
amount = 5
recipients = [["a", 10], ["b", 10], ["c", 10]]
result = [["a", 2], ["b", 2], ["c", 1]]
```
`pool = 5`. `sum(min(owed, 1)) = 3 <= 5` but `sum(min(owed, 2)) = 6 > 5`, so `L = 1` and `R = 5 - 3 = 2`. All three are above `L`, so the two leftover units go to the first two in input order.
```text
amount = 20
recipients = [["a", 3], ["b", 4]]
result = [["a", 3], ["b", 4]]
```
`pool = min(20, 7) = 7 = sum(owed)`, so `L = 4`, `R = 0`, and both are paid in full. The 13 unspent units simply stay in the pool.
### Language interface
Each language uses the shape its harness marshals naturally; the semantics above are identical everywhere.
- **Python** -- `distribute(amount, recipients)`; `recipients` is a list of `[str, int]` two-element lists, and you return a list of `[str, int]` two-element lists.
- **JavaScript** -- `distribute(amount, recipients)`; rows are two-element arrays `[string, number]`.
- **Java** -- `java.util.List<java.util.List<Object>> distribute(long amount, java.util.List<java.util.List<Object>> recipients)`. Each input row holds a `String` id at index 0 and a `Number` (`Integer` or `Long`, depending on magnitude -- read it with `((Number) row.get(1)).longValue()`) at index 1. Each output row must hold the `String` id and a `Long` payment.
- **C++** -- `std::vector<std::pair<std::string, long long>> distribute(long long amount, const std::vector<std::pair<std::string, long long>>& recipients)`; `.first` is the id and `.second` is the amount.
### Follow-up thinking
- Why does dividing `pool` evenly and then clipping each share to `owed` fail to exhaust the pool?
- Why does sorting by `owed` reveal `L` without ever touching individual currency units?
- Why is the original input order still needed after `L` is known?
Constraints
- 1 <= recipients.length <= 200,000
- 0 <= amount <= 1,000,000,000,000 (10^12)
- 0 <= owed <= 10,000,000,000 (10^10) for every recipient
- Each recipient_id contains 1 to 64 ASCII characters, and all ids are unique
- sum(owed) can reach 200,000 * 10^10 = 2 * 10^15, so every running total, level-search product and prefix sum must use 64-bit integers: Java `long`, C++ `long long`. A 32-bit accumulator overflows by six orders of magnitude.
- Every stated value and intermediate sum stays below 2^53, so JavaScript `Number` remains exact and no BigInt or modular workaround is needed.
- Use exact integer arithmetic only; payments are whole minor currency units.
- Target O(n log n) time and O(n) auxiliary space. Do not distribute one currency unit at a time -- amount can be as large as 10^12.
Examples
Input: (40, [['a', 10], ['b', 10], ['c', 10], ['d', 10]])
Expected Output: [['a', 10], ['b', 10], ['c', 10], ['d', 10]]
Input: (10, [['a', 2], ['b', 8], ['c', 8]])
Expected Output: [['a', 2], ['b', 4], ['c', 4]]
Hints
- Sort the owed amounts. The smallest debts are the first to be fully satisfied, and once a recipient is capped they leave the group that is still sharing the remaining money.
- Walk the sorted caps and ask at each step whether the whole remaining group can be raised to the next cap. The first step you cannot afford is where the water level settles, and one integer division finishes it.
- Two orders are in play: the sorted order finds the level, but the answer -- including which recipients receive the leftover single units -- is reported in the original input order.