LeetCode 3711 - Maximum Transactions Without Negative Balance
Here is a complete, detailed technical solution guide for LeetCode 3711 - Maximum Transactions Without Negative Balance, following your formatting instructions precisely. The problem provides an array transactions of integers representing sequential money movements in an account.
Difficulty: 🟡 Medium
Topics: Array, Greedy, Heap (Priority Queue)
Solution
Here is a complete, detailed technical solution guide for LeetCode 3711 - Maximum Transactions Without Negative Balance, following your formatting instructions precisely.
Problem Understanding
The problem provides an array transactions of integers representing sequential money movements in an account. Positive values correspond to receiving money, while negative values correspond to sending money. The account starts at a balance of zero, and we are not allowed to let the balance become negative at any point. The goal is to perform as many transactions as possible in order, skipping some if necessary, without violating the non-negative balance constraint.
The input constraints are substantial: the length of transactions can reach up to 100,000 elements, and transaction values can range from -10^9 to 10^9. This indicates that any brute-force solution that attempts all subsets of transactions will be infeasible due to exponential time complexity.
Important edge cases include arrays with only negative transactions, arrays that start with a large negative number, or alternating large negative and positive transactions. The solution must handle large sums safely without integer overflow.
The expected output is a single integer: the maximum number of transactions that can be executed without the balance dropping below zero.
Approaches
Brute Force
The brute-force approach is to consider all subsets of transactions in order, checking for each whether applying the transactions sequentially would maintain a non-negative balance. For each valid sequence, we count the number of transactions included and return the maximum. This guarantees correctness but is computationally infeasible: with n transactions, there are 2^n subsets. Given the constraints (n <= 10^5), this is completely impractical.
Optimal Approach
The key observation is that any positive transaction can always be included, since it increases the balance. The challenge lies with negative transactions: we can only include a negative transaction if the current balance is at least as large as its absolute value.
A greedy strategy emerges: process transactions in order, maintaining a running balance. Whenever we encounter a negative transaction that would make the balance negative, we can consider removing the largest previously included negative transaction to make room. To do this efficiently, we can maintain a min-heap of negative transaction magnitudes we have accepted. If a new negative transaction cannot be applied directly, we check the heap: if the new negative is smaller than the largest negative we included so far, we replace it. This ensures we maximize the number of transactions by minimizing the impact on the balance.
This approach leverages a priority queue (min-heap) and a running sum for efficient greedy selection.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(2^n) | O(n) | Check all subsets of transactions for valid sequences |
| Optimal | O(n log n) | O(n) | Greedy with min-heap to track included negative transactions |
Algorithm Walkthrough
-
Initialize
balance = 0,count = 0, and a min-heapnegatives = []to track the included negative transactions. -
Iterate through each transaction
tintransactions. -
If
tis positive, simply add it tobalanceand incrementcount. -
If
tis negative: -
If
balance + t >= 0, include it: addttobalance, push-tintonegatives(store as positive for easy heap comparison), and incrementcount. -
Else, check if there is a previously included larger negative transaction (
max_neg = heapq.nlargest(1, negatives)or maintain a max-heap using negative values). If such exists and-t < max_neg, replace it: remove the largest negative from heap, adjustbalanceaccordingly, add current negative instead. -
Continue this process until all transactions are processed.
Why it works:
The algorithm maintains the invariant that balance never drops below zero. By greedily replacing larger negative transactions with smaller ones, we ensure that every transaction included minimally impacts the balance, allowing the maximum number of transactions.
Python Solution
from typing import List
import heapq
class Solution:
def maxTransactions(self, transactions: List[int]) -> int:
balance = 0
count = 0
# Min-heap to store the negative transactions we have included (as positive values)
heap = []
for t in transactions:
if t >= 0:
balance += t
count += 1
else:
if balance + t >= 0:
balance += t
heapq.heappush(heap, -t) # store absolute value
count += 1
elif heap and -heap[0] > -t:
# Replace the largest negative with this smaller negative
removed = -heapq.heappop(heap)
balance += removed # undo previous large negative
balance += t # apply current smaller negative
heapq.heappush(heap, -t)
return count
Explanation:
The code iterates through each transaction, applying positive transactions immediately. For negative transactions, it uses a min-heap to track the absolute values of negative transactions we have included. If a new negative transaction cannot be applied due to insufficient balance, we check if replacing a previously included larger negative transaction allows inclusion, maintaining the greedy property.
Go Solution
package main
import (
"container/heap"
)
type IntHeap []int
func (h IntHeap) Len() int { return len(h) }
func (h IntHeap) Less(i, j int) bool { return h[i] > h[j] } // max-heap
func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *IntHeap) Push(x any) { *h = append(*h, x.(int)) }
func (h *IntHeap) Pop() any {
old := *h
n := len(old)
x := old[n-1]
*h = old[:n-1]
return x
}
func maxTransactions(transactions []int) int {
balance := 0
count := 0
h := &IntHeap{}
heap.Init(h)
for _, t := range transactions {
if t >= 0 {
balance += t
count++
} else {
if balance+t >= 0 {
balance += t
heap.Push(h, -t)
count++
} else if h.Len() > 0 && (*h)[0] > -t {
removed := heap.Pop(h).(int)
balance += removed // undo previous large negative
balance += t // apply current smaller negative
heap.Push(h, -t)
}
}
}
return count
}
Explanation:
Go uses a custom IntHeap as a max-heap to track negative transactions (stored as positive values). The rest of the logic mirrors the Python solution, adjusting for Go syntax and type safety. Using container/heap ensures efficient O(log n) operations.
Worked Examples
Example 1: transactions = [2, -5, 3, -1, -2]
| Step | Transaction | Balance | Heap | Count |
|---|---|---|---|---|
| 1 | 2 | 2 | [] | 1 |
| 2 | -5 | 2-5=-3 | [] | 1 (skip, negative) |
| 3 | 3 | 5 | [] | 2 |
| 4 | -1 | 4 | [1] | 3 |
| 5 | -2 | 2 | [1,2] | 4 |
Output: 4
Example 2: transactions = [-1, -2, -3]
All are negative, cannot apply any without going below zero.
Output: 0
Example 3: transactions = [3, -2, 3, -2, 1, -1]
| Step | Transaction | Balance | Heap | Count |
|---|---|---|---|---|
| 1 | 3 | 3 | [] | 1 |
| 2 | -2 | 1 | [2] | 2 |
| 3 | 3 | 4 | [2] | 3 |
| 4 | -2 | 2 | [2,2] | 4 |
| 5 | 1 | 3 | [2,2] | 5 |
| 6 | -1 | 2 | [2,2,1] | 6 |
Output: 6
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n log n) | Each transaction is processed once, heap operations for negative transactions take O(log n) |
| Space | O(n) | Heap may contain all negative transactions in worst case |
The heap ensures we efficiently maintain the largest negative transaction seen so far for potential replacement, which is the main factor in the O(log n) term.
Test Cases
# Provided examples
assert Solution().maxTransactions([2,-5,3,-1