LeetCode 860 - Lemonade Change
This problem simulates a lemonade stand where each lemonade costs 5, 20 bill. The challenge is to provide exact change to every customer as they pay. The input is a list of integers bills, where each element represents the bill a customer gives.
Difficulty: 🟢 Easy
Topics: Array, Greedy
Solution
Problem Understanding
This problem simulates a lemonade stand where each lemonade costs $5. Customers come in a specific order and pay with a $5, $10, or $20 bill. The challenge is to provide exact change to every customer as they pay. The input is a list of integers bills, where each element represents the bill a customer gives. The output is a boolean: true if it is possible to give correct change to every customer, and false otherwise.
Constraints indicate that the array can be very large, up to 100,000 elements. Each bill is guaranteed to be one of 5, 10, or 20, so we do not need to handle invalid denominations. The problem also implies that we start with zero cash on hand, so our initial transactions are restricted to exact payments or accumulation of small bills for future change.
Edge cases that can trip a naive solution include consecutive high-value bills, such as [10, 20] without an initial $5, or sequences where $20 bills appear before sufficient $10 and $5 bills are collected.
Approaches
The brute-force approach would attempt to simulate every possible way to give change, trying all combinations of available bills to satisfy each customer. This is unnecessary because the problem is constrained to only three bill types and the cost is fixed at $5. While correct, such a brute-force approach could involve recursive checking or backtracking, which is inefficient for large input sizes.
The key insight is that a greedy strategy works here: always give change using the largest bills available. For a $10 payment, give one $5. For a $20 payment, prioritize giving one $10 and one $5 if possible; otherwise, give three $5 bills. This ensures we preserve smaller bills for future transactions and avoids running out of necessary change.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(2^n) | O(n) | Consider all combinations of giving change; correct but infeasible for large n |
| Optimal | O(n) | O(1) | Greedy simulation using counters for $5 and $10 bills; linear pass through input |
Algorithm Walkthrough
- Initialize two counters,
fiveandten, to zero. These will track the number of$5and$10bills in hand. - Iterate over each
billin thebillsarray. - If
billis$5, increment thefivecounter. No change is needed. - If
billis$10, check if at least one$5is available. If yes, decrementfiveby one and incrementtenby one. If no$5is available, returnfalse. - If
billis$20, first try to give one$10and one$5if both are available. If not, try giving three$5bills. If neither option is possible, returnfalse. - If the loop completes without returning
false, all customers received correct change. Returntrue.
Why it works: The greedy approach ensures that the largest bills are used for change first, preserving smaller bills for subsequent transactions. The algorithm maintains an invariant: at any point, the counters reflect the exact number of bills available for change, which guarantees correctness.
Python Solution
from typing import List
class Solution:
def lemonadeChange(self, bills: List[int]) -> bool:
five = 0
ten = 0
for bill in bills:
if bill == 5:
five += 1
elif bill == 10:
if five == 0:
return False
five -= 1
ten += 1
else: # bill == 20
if ten > 0 and five > 0:
ten -= 1
five -= 1
elif five >= 3:
five -= 3
else:
return False
return True
The Python implementation follows the algorithm exactly. Counters for $5 and $10 track available bills, and conditional checks handle each type of incoming bill. The greedy choice ensures correct change is given without using extra space or complex data structures.
Go Solution
func lemonadeChange(bills []int) bool {
five, ten := 0, 0
for _, bill := range bills {
switch bill {
case 5:
five++
case 10:
if five == 0 {
return false
}
five--
ten++
case 20:
if ten > 0 && five > 0 {
ten--
five--
} else if five >= 3 {
five -= 3
} else {
return false
}
}
}
return true
}
In Go, we use a switch statement for clarity and maintain two integer counters. Slices handle iteration efficiently, and there is no need for dynamic allocation beyond the input slice. The logic mirrors the Python version.
Worked Examples
Example 1: [5,5,5,10,20]
| Step | Bill | Five Count | Ten Count | Action |
|---|---|---|---|---|
| 1 | 5 | 1 | 0 | Collect $5 |
| 2 | 5 | 2 | 0 | Collect $5 |
| 3 | 5 | 3 | 0 | Collect $5 |
| 4 | 10 | 2 | 1 | Give one $5 change, collect $10 |
| 5 | 20 | 1 | 0 | Give one $10 and one $5 change |
All customers receive change. Return true.
Example 2: [5,5,10,10,20]
| Step | Bill | Five Count | Ten Count | Action |
|---|---|---|---|---|
| 1 | 5 | 1 | 0 | Collect $5 |
| 2 | 5 | 2 | 0 | Collect $5 |
| 3 | 10 | 1 | 1 | Give $5 change, collect $10 |
| 4 | 10 | 0 | 2 | Give $5 change, collect $10 |
| 5 | 20 | 0 | 2 | Cannot give $15 change |
Return false.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Single pass through the bills array of length n |
| Space | O(1) | Only two integer counters are used regardless of input size |
The linear time complexity is optimal because each bill must be checked. Space is constant because we do not need any dynamic data structures to store intermediate states.
Test Cases
# Provided examples
assert Solution().lemonadeChange([5,5,5,10,20]) == True # basic valid sequence
assert Solution().lemonadeChange([5,5,10,10,20]) == False # insufficient $5 for final $20
# Edge cases
assert Solution().lemonadeChange([5]) == True # only one customer, exact change
assert Solution().lemonadeChange([10]) == False # first customer pays $10, no change
assert Solution().lemonadeChange([20]) == False # first customer pays $20, no change
assert Solution().lemonadeChange([5,10,5,10,20]) == False # early $10 prevents change for $20
assert Solution().lemonadeChange([5,5,5,5,10,5,10,10,10,20]) == False # complex sequence failing at end
assert Solution().lemonadeChange([5,5,5,5,5,5,20,20,20,20]) == False # too many $20s early
| Test | Why |
|---|---|
[5,5,5,10,20] |
Valid sequence, change is always available |
[5,5,10,10,20] |
Fails due to insufficient $5 for $20 |
[5] |
Single customer edge case |
[10] |
Single $10 payment without $5 available |
[20] |
Single $20 payment without $5 available |
[5,10,5,10,20] |
Early $10 prevents proper change later |
[5,5,5,5,10,5,10,10,10,20] |
Complex sequence failing at last $20 |
[5,5,5,5,5,5,20,20,20,20] |
Multiple $20s early with insufficient change |
Edge Cases
The first important edge case is when the first customer pays with a $10 or $20 bill. Since we start with zero change, it is impossible to satisfy these transactions, and the implementation correctly returns false.
The second edge case occurs when a $20 payment arrives, and we have multiple combinations of bills to provide change. The greedy strategy ensures we always use one $10 and one $5 if possible, preserving smaller bills for future $10 payments, which avoids running out of change.
The third edge case involves a long sequence of $5 bills followed by several $20 bills. Without careful accounting, one might attempt