Table of contents
Description
You are given an array of transactions transactions where transactions[i] = [from_i, to_i, amount_i] indicates that the person with ID = from_i gave amount_i $ to the person with ID = to_i.
Return the minimum number of transactions required to settle the debt.
Example 1:
Input: transactions = [[0,1,10],[2,0,5]]
Output: 2
Explanation:
Person #0 gave person #1 $10.
Person #2 gave person #0 $5.
Two transactions are needed. One way to settle the debt is person #1 pays person #0 and #2 $5 each.
Example 2:
Input: transactions = [[0,1,10],[1,0,1],[1,2,5],[2,0,5]]
Output: 1
Explanation:
Person #0 gave person #1 $10.
Person #1 gave person #0 $1.
Person #1 gave person #2 $5.
Person #2 gave person #0 $5.
Therefore, person #1 only need to give person #0 $4, and all debt is settled.
Constraints:
1 <= transactions.length <= 8transactions[i].length == 30 <= from_i, to_i < 12from_i != to_i1 <= amount_i <= 100
Solution
Idea1
We could solve this question with the subset sum method.
- We calculate the total net balances for each person and collect those with non-zero balances. Assume this include persons
0, 1, 2. - We use an array
f[i]to represent the minimum number of edges (transactions) needed for people in subseti, where personjis in subsetiif1<<j & i != 0. Foe example,f[3]represents subset3(11bin binary format) includes person0and1because3 == 1<<0 + 1<<1. let n == number of non-zero balances. We use dynamic programming to minimize the transactions needed, i.e., thef[]array. We iterateiin[1,1<<n)(non-empty subsets:i!=0) for all the possible subsets of people. In the inner loop, we check if personjis in the current subsetiand calculate the total balance for subseti. If the total is0, we can minimizef[i]by separating subsetiinto two groups (jandi^j).- Finally we return the minimized
f[1<<n-1]orf[-1]which is the subset including all the people with non-zero balances.11...1with1repeatingntimes.
Complexity: Time O(n*2^n), Space O(2^n).
Java
Stay tuned.
Python
class Solution1:
def balance_graph(self, edges: List[List[int]]) -> int:
"""
@param edges: a directed graph where each edge is represented by a tuple
@return: the number of edges
"""
bal = defaultdict(int)
for f, t, amount in edges:
bal[f] -= amount
bal[t] += amount
non_zero = [b for b in bal.values() if b]
n = len(non_zero)
f = [sys.maxsize] * (1 << n) # python2 sys.maxint, python3 sys.maxsize, inf, or 1<<29
f[0] = 0
for i in range(1, 1 << n):
total = 0 # total balances in this subset
for j, x in enumerate(non_zero):
if i >> j & 1:
total += x # bit_cnt += 1 for py < 3.10
if total == 0:
f[i] = i.bit_count() - 1 # bit_count() needs python 3.10
j = (i - 1) & i
while j > 0:
f[i] = min(f[i], f[j] + f[j ^ i])
j = (j - 1) & i
return f[-1] # f[(1<<n)-1]
Idea2
Similar to Idea1 above, we can collect people with non-zero balances and try to settle the transactions among them. In this method, we can use backtracking for settling the transactions. The method can pass on LeetCode but not on LintCode because of the factorial time complexity.
- Optionally, we can sort the non-zero balances descending to optimize the run time. Note this does not change the overall time complexity.
- We recursively try to minimize the transactions starting from person 0.
- In the dfs method, we iterate through the indexes
iafter the starting index and try to settle the balance ifnon_zero[i]andnon_zero[start]are of opposite signs. We use one transaction to addnon_zero[i]tonon_zero[start]and recursively dfsstart+1and take the minimum. After the recursive dfs, we backtrack to setnon_zero[i]to the original value. - If the settled balance is 0, we can reduce the number of transactions so we can break out of the for loop.
Complexity: Time O(n!), Space O(n).
Java
Stay tuned.
Python
class Solution2:
def minTransfers(self, edges: List[List[int]]) -> int:
bal = defaultdict(int)
for u, v, w in edges:
bal[u] -= w
bal[v] += w
non_zero = [b for b in bal.values() if b != 0]
non_zero.sort(reverse=True)
# Use DFS with backtracking to minimize transactions
def dfs(start: int) -> int:
while start < len(non_zero) and non_zero[start] == 0:
start += 1
# Base case: All balances settled
if start == len(non_zero):
return 0
res = float('inf')
# Try to settle balances[start] with subsequent balances
for i in range(start + 1, len(non_zero)):
if non_zero[i] * non_zero[start] < 0: # Opposite signs only
# Settle balances[start] with balances[i]
non_zero[i] += non_zero[start]
# Recur to settle the next balance and count this transaction
res = min(res, 1 + dfs(start + 1))
# Backtrack to previous state
non_zero[i] -= non_zero[start]
# Optimization: Stop if an exact zero balance is found
if non_zero[i] + non_zero[start] == 0:
break
return res
return dfs(0)