Greedy · 11 of 35
Minimize Cash Flow among a given set of friends who have borrowed money from each other
Friends owe each other money in a tangle of IOU notes, so work out who is up and who is down overall and settle with as little money moving as possible.
- Simple way
- time O(m + n^2)
room O(n) - Clever way
- time O(m + n log n)
room O(n)
The problem, simply
There are n friends and a list of IOUs. Each IOU says "friend a owes friend b some amount". Some friends owe and are owed at the same time, so paying every IOU moves a lot of money back and forth.
Find a set of payments that leaves everyone even and moves the least total money. Print who pays whom and how much.
A story to remember it
Picture it
The IOU notes as arrows. Ann to Ben with 40 means Ann owes Ben 40. Six notes, 160 in total.
Examples
Three friends
- In
0 owes 1: 1000, 0 owes 2: 2000, 1 owes 2: 5000- Out
1 pays 2: 4000, 0 pays 2: 3000
Friend 0 is down 3000, friend 1 is down 4000, friend 2 is up 7000. Two payments, 7000 in total, instead of 8000.
Five friends
- In
Ann owes Ben 40, Ben owes Cat 20, Cat owes Dan 50, Dan owes Ann 10, Eve owes Ann 30, Ben owes Eve 10- Out
Cat pays Dan 30, Eve pays Ben 10, Eve pays Dan 10
Ann is even and pays nothing. Three payments move 50 instead of 160.
A circle of debts
- In
A owes B 10, B owes C 10, C owes A 10- Out
no payments
Everyone owes 10 and is owed 10, so every balance is 0.
Nobody owes anything
- In
no IOUs- Out
no payments
All balances are 0 from the start.
Way 1: the simple way
First turn the tangle into one balance per friend. Then, each round, scan everyone to find the friend owed the most and the friend who owes the most. The one who owes pays the smaller of the two amounts, so at least one of them becomes even.
- For each IOU
a owes b x:net[a] -= xandnet[b] += x. - Scan for
max(largest balance) andmin(most negative balance). - If
net[max]is 0, everyone is even. Stop. - Pay
amount = min(net[max], -net[min])frommintomax, update both, and go back to step 2.
import java.util.*;
class Solution {
// each debt is {from, to, amount}; each payment is {payer, receiver, amount}
public List<int[]> settle(int n, int[][] debts) {
int[] net = new int[n];
for (int[] d : debts) {
net[d[0]] -= d[2]; // the one who owes goes down
net[d[1]] += d[2]; // the one who is owed goes up
}
List<int[]> payments = new ArrayList<>();
while (true) {
int max = 0, min = 0;
for (int i = 1; i < n; i++) {
if (net[i] > net[max]) max = i;
if (net[i] < net[min]) min = i;
}
if (net[max] == 0) break; // everyone is even
int amount = Math.min(net[max], -net[min]);
payments.add(new int[]{min, max, amount});
net[max] -= amount;
net[min] += amount;
}
return payments;
}
}How long it takes: building the balances is O(m) for m IOUs. Every payment makes at least one friend even, so there are at most n - 1 rounds, and each round scans n friends. That is O(m + n^2). Extra room: the balances, O(n).
Way 2: the clever way
The scans are the slow part. Keep the friends who are owed money in one max-heap and the friends who owe money in another, both ordered by amount. A heap is a pile that always hands you its biggest item quickly.
- Build the balances as before.
- Put every positive balance in the
gettersheap and every negative balance, as a positive number, in thegiversheap. Friends at 0 are already done. - While
gettersis not empty: take the top of each heap. The giver paysamount = minof the two. Whoever still has money left goes back into their heap.
Balances first, then the biggest giver pays the biggest getter
01 / 07
Six IOU notes. An arrow from Ann to Ben with 40 means Ann owes Ben 40. Paying every note would move 160 in 6 payments.
- looking now
- checking
- done
- answer
- just added
- ignored
import java.util.*;
class Solution {
public List<int[]> settle(int n, int[][] debts) {
int[] net = new int[n];
for (int[] d : debts) {
net[d[0]] -= d[2];
net[d[1]] += d[2];
}
// entries are {amount, friend}: biggest amount first, then the smaller friend number
Comparator<int[]> biggest = (x, y) -> x[0] != y[0] ? Integer.compare(y[0], x[0]) : Integer.compare(x[1], y[1]);
PriorityQueue<int[]> getters = new PriorityQueue<>(biggest);
PriorityQueue<int[]> givers = new PriorityQueue<>(biggest);
for (int i = 0; i < n; i++) {
if (net[i] > 0) getters.add(new int[]{net[i], i});
else if (net[i] < 0) givers.add(new int[]{-net[i], i});
}
List<int[]> payments = new ArrayList<>();
while (!getters.isEmpty()) {
int[] g = getters.poll();
int[] d = givers.poll();
int amount = Math.min(g[0], d[0]);
payments.add(new int[]{d[1], g[1], amount}); // d pays g
if (g[0] > amount) getters.add(new int[]{g[0] - amount, g[1]});
if (d[0] > amount) givers.add(new int[]{d[0] - amount, d[1]});
}
return payments;
}
}How long it takes: O(m) for the balances, then at most n - 1 payments, each with a few heap steps of O(log n). Total O(m + n log n). Extra room: the balances and the heaps, O(n).
Another way: the fewest payments
The greedy always moves the least money: exactly the sum of the positive balances, which no plan can beat. It also never needs more than n - 1 payments. But it does not always use the fewest payments.
Take balances -3, -2, -2, +3, +4. The greedy pairs +4 with -3 first and needs 4 payments. Better: the -3 pays the +3, and the two -2 friends pay the +4. That is 3 payments. Finding the fewest payments in general means splitting the friends into as many groups as possible where each group adds up to 0, which is a hard search problem. For small n you can try every way:
class Solution {
public int fewestPayments(int n, int[][] debts) {
int[] net = new int[n];
for (int[] d : debts) {
net[d[0]] -= d[2];
net[d[1]] += d[2];
}
return search(net, 0);
}
// settle friend i with some later friend of the other sign, then move on
private int search(int[] net, int i) {
while (i < net.length && net[i] == 0) i++;
if (i == net.length) return 0;
int best = Integer.MAX_VALUE;
for (int j = i + 1; j < net.length; j++) {
if ((net[i] > 0) != (net[j] > 0) && net[j] != 0) {
net[j] += net[i]; // i hands their whole balance to j
best = Math.min(best, 1 + search(net, i + 1));
net[j] -= net[i]; // undo
}
}
return best;
}
}This tries up to O(n!) orders, so it is only for about a dozen friends. It is the LeetCode problem "Optimal Account Balancing".
Watch out for
- Build balances first. Settling IOU by IOU keeps the circles of debt.
- Friends with balance 0 must not enter a heap, or you will make payments of 0.
- The balances always add up to 0, so when the getters heap is empty the givers heap is empty too.
- Large sums can overflow
int. Uselongif amounts are big. - If the input is an
nbyntable instead of a list, building balances costsO(n^2), and that dominates.
What interviewers ask next
- Why is the money moved minimal? Every getter must receive at least their balance, so any plan moves at least the sum of positive balances, and the greedy moves exactly that.
- Why at most
n - 1payments? Each payment makes at least one friend even, and the last two friends become even together. - How would you build this for a real app like a shared expense tracker? Keep running balances per friend as expenses come in, and only run the settle step when someone asks to settle up.
- Can you do it without heaps in
O(n log n)? Sort the givers and getters by amount and walk two pointers from the big ends. You get a valid plan with at mostn - 1payments, just not always the same pairing.
Remember this
Grown-up words: the friends and IOU arrows are a directed weighted graph, the balance is the net flow of each node, the two piles are max-heaps (priority queues), and splitting into zero-sum groups is an NP-hard partition problem.