Graph · 42 of 43
Minimise the cashflow among a given set of friends who have borrowed money from each other
After a trip full of small loans between friends, settle up with as little money changing hands as possible.
- Simple way
- time O(N^2)
room O(1) - Clever way
- time O(N^2)
room O(N)
The problem, simply
N friends lent each other money. You get a table: owes[i][j] is how much friend i owes friend j.
Find a list of payments that makes everyone even, so that the total money moved is as small as possible. The classic solution also uses at most N - 1 payments. Print or return the payments.
A story to remember it
Picture it
Five friends and six debts. The arrow 0 to 2 with 20 means friend 0 owes friend 2 twenty. Paying as written moves 65 coins in 6 payments.
Examples
Three friends
- In
owes = [[0,1000,2000],[0,0,5000],[0,0,0]]- Out
1 pays 2 4000, 0 pays 2 3000
Nets: friend 0 is -3000, friend 1 is 1000 - 5000 = -4000, friend 2 is +7000. Two payments instead of three, and 7000 moves instead of 8000.
A chain
- In
0 owes 1 ten, 1 owes 2 ten- Out
0 pays 2 10
Friend 1 gets 10 and pays 10, so their net is 0. Friend 0 can pay friend 2 directly.
A circle
- In
0 owes 1 five, 1 owes 2 five, 2 owes 0 five- Out
no payments
Everyone gets 5 and pays 5. All nets are 0, so nobody needs to pay anything.
The picture above
- In
five friends, debts 0>1:10, 0>2:20, 1>2:5, 1>3:15, 3>4:10, 4>0:5- Out
0 pays 2 25, 1 pays 3 5, 1 pays 4 5
Three payments moving 35 coins, instead of six payments moving 65.
Way 1: the simple way
Pay every debt as written, but first cancel debts that go both ways: if i owes j 30 and j owes i 10, then i pays j 20 and that is it.
- For every pair
i < j, computed = owes[i][j] - owes[j][i]. - If
d > 0,ipaysjthe amountd. Ifd < 0,jpaysithe amount-d.
import java.util.*;
class Solution {
// each payment is {from, to, amount}
public List<int[]> settle(int[][] owes) {
int n = owes.length;
List<int[]> pays = new ArrayList<>();
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
int d = owes[i][j] - owes[j][i]; // cancel back and forth
if (d > 0) pays.add(new int[]{i, j, d});
else if (d < 0) pays.add(new int[]{j, i, -d});
}
}
return pays;
}
}How long it takes: one look at every pair, O(N^2). Extra room besides the answer: O(1). But it can need up to N * (N - 1) / 2 payments, and money goes round in circles: in the circle example, it makes three payments when none are needed.
Way 2: the clever way
Only the net amount of each friend matters. Who owed whom along the way does not.
- For each friend,
net[i]= everything others oweiminus everythingiowes others. The nets always add up to 0. - Find the biggest receiver
cr(largestnet) and the biggest payerdr(smallestnet). - If
net[cr]is 0, everyone is even: stop. drpayscrthe amountmin(net[cr], -net[dr]). Update both nets and repeat.
Each payment makes at least one of the two friends exactly even, and an even friend never pays or receives again. So there are at most N - 1 payments. The money moved is the sum of all positive nets, which is the least possible: every receiver must get at least their net.
Squash debts into nets, then biggest payer pays biggest receiver
01 / 07
Here is who owes whom. An arrow 0 to 2 with 20 means friend 0 owes friend 2 twenty coins. Paying every debt as written takes 6 payments and moves 65 coins.
- looking now
- checking
- done
- answer
- just added
import java.util.*;
class Solution {
// each payment is {from, to, amount}
public List<int[]> minCashFlow(int[][] owes) {
int n = owes.length;
int[] net = new int[n];
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
net[i] += owes[j][i] - owes[i][j]; // what i gets minus what i pays
List<int[]> pays = new ArrayList<>();
while (true) {
int cr = 0, dr = 0;
for (int i = 1; i < n; i++) {
if (net[i] > net[cr]) cr = i; // biggest receiver
if (net[i] < net[dr]) dr = i; // biggest payer
}
if (net[cr] == 0) break; // everyone is even
int amount = Math.min(net[cr], -net[dr]);
net[cr] -= amount;
net[dr] += amount;
pays.add(new int[]{dr, cr, amount});
}
return pays;
}
}How long it takes: building the nets reads the whole table, O(N^2). Then at most N - 1 payments, each finding the max and min in O(N), which is also O(N^2). Extra room: the net array, O(N).
Watch out for
- Getting the sign backwards. Pick one rule and stick to it:
net[i]is whatishould receive minus whatishould pay. - Using
net[cr] == 0as the stop test only works because the nets sum to 0: if the biggest receiver is even, nobody is owed anything, so nobody owes anything either. - Large amounts. If debts can reach billions, use
longfor the nets. - The recursive version from GeeksforGeeks is the same idea. A loop avoids a deep call stack.
What interviewers ask next
- Can you make the matching faster? Put receivers in a max-heap and payers in a max-heap of debts. Each payment then costs
O(log N), so settling isO(N log N)after the nets are built. - Why is the moved money minimal? Each receiver must get at least their net, so the total moved is at least the sum of positive nets, and greedy moves exactly that.
- Fewest number of payments? Split the friends into as many groups as possible where each group's nets sum to 0. A group of
kpeople needsk - 1payments, so the answer isN - (number of groups). With up to about 12 people, a bitmask search finds it. - How would Splitwise do it? Exactly this: compute nets per person, then match payers and receivers greedily, often with heaps.
Remember this
Grown-up words: friends are vertices, debts are directed weighted edges, the net is the balance (incoming minus outgoing weight), plus friends are creditors, minus friends are debtors, and always settling the extremes is a greedy algorithm.