questions F-L

This commit is contained in:
2025-05-25 11:47:04 +01:00
parent 360b5fa255
commit ad320dc703
54 changed files with 11235 additions and 0 deletions

View File

@@ -0,0 +1,177 @@
title: Lemonade Change
slug: lemonade-change
difficulty: easy
leetcode_id: 860
leetcode_url: https://leetcode.com/problems/lemonade-change/
categories:
- arrays
patterns:
- greedy
description: |
At a lemonade stand, each lemonade costs `$5`. Customers are standing in a queue to buy from you and order one at a time (in the order specified by `bills`). Each customer will only buy one lemonade and pay with either a `$5`, `$10`, or `$20` bill. You must provide the correct change to each customer so that the net transaction is that the customer pays `$5`.
Note that you do not have any change in hand at first.
Given an integer array `bills` where `bills[i]` is the bill the i<sup>th</sup> customer pays, return `true` *if you can provide every customer with the correct change*, or `false` *otherwise*.
constraints: |
- `1 <= bills.length <= 10^5`
- `bills[i]` is either `5`, `10`, or `20`
examples:
- input: "bills = [5,5,5,10,20]"
output: "true"
explanation: "From the first 3 customers, we collect three $5 bills. From the fourth customer, we collect a $10 bill and give back a $5. From the fifth customer, we give a $10 bill and a $5 bill. Since all customers got correct change, we output true."
- input: "bills = [5,5,10,10,20]"
output: "false"
explanation: "From the first two customers, we collect two $5 bills. For the next two customers, we collect a $10 bill and give back a $5 bill each. For the last customer, we cannot give the change of $15 back because we only have two $10 bills (no $5 bills left)."
explanation:
intuition: |
Imagine you're actually running a lemonade stand with a cash register. You start with an empty register and customers line up to pay.
The key insight is that **$5 bills are the most valuable** — not because of their face value, but because they're the most *versatile* for making change. A $5 bill can be used to:
- Give change for a $10 bill (need one $5)
- Give change for a $20 bill (need one $10 + one $5, OR three $5s)
Think of it like this: when a customer pays with $20, you have two options for the $15 change:
1. One $10 + one $5 (preferred — uses the less versatile $10)
2. Three $5 bills (backup — depletes your precious $5s)
The greedy choice is always to **preserve your $5 bills** when possible. Use $10 bills first when giving change for $20, because $10 bills can only be used for one purpose (change for $20), while $5 bills can be used for both $10 and $20 transactions.
approach: |
We solve this using a **Greedy Simulation**:
**Step 1: Initialise counters**
- `fives`: Count of $5 bills in hand, starts at `0`
- `tens`: Count of $10 bills in hand, starts at `0`
- We don't need to track $20 bills — they can never be used as change
&nbsp;
**Step 2: Process each customer in order**
- **If customer pays $5**: No change needed. Increment `fives` by 1
- **If customer pays $10**: Need to give $5 change. If `fives == 0`, return `false`. Otherwise, decrement `fives`, increment `tens`
- **If customer pays $20**: Need to give $15 change. Try the greedy choice first:
- If we have at least one $10 and one $5: use them (decrement both)
- Else if we have at least three $5s: use them (decrement `fives` by 3)
- Else: return `false` — we can't make change
&nbsp;
**Step 3: Return the result**
- If we process all customers successfully, return `true`
&nbsp;
The greedy strategy works because using a $10 bill when available always leaves us in a better (or equal) position than using three $5 bills.
common_pitfalls:
- title: Not Prioritising $10 Bills for $20 Change
description: |
When giving change for $20, some might randomly choose between using three $5s or one $10 + one $5. This can lead to failure.
For example, with `bills = [5,5,10,20,5,5,5,10,20,20]`:
- If you use three $5s for the first $20 instead of $10 + $5, you might run out of $5 bills later when a $10 customer arrives
Always prefer using $10 bills for $20 change — $5 bills are more versatile.
wrong_approach: "Random or first-available bill selection"
correct_approach: "Greedy: prefer $10 + $5 over three $5s"
- title: Tracking $20 Bills
description: |
It's tempting to track all bill types, but $20 bills are useless for making change. You can never give a $20 bill back to a customer (since the most change needed is $15).
Tracking $20 bills wastes space and adds unnecessary complexity.
wrong_approach: "Maintaining a counter for $20 bills"
correct_approach: "Only track $5 and $10 bills"
- title: Forgetting the Empty Register Start
description: |
The problem states you start with no change. If the first customer pays with anything other than $5, you immediately fail.
For input `bills = [10,5,5,5,5]`, the answer is `false` because you can't give change to the very first customer.
wrong_approach: "Assuming some initial change is available"
correct_approach: "Start with fives = 0 and tens = 0"
key_takeaways:
- "**Greedy pattern**: When multiple valid choices exist, prefer the one that keeps more options open (preserve $5 bills)"
- "**Simulation**: Sometimes the best approach is to simulate the process step by step"
- "**Track only what matters**: $20 bills are never used as change, so don't track them"
- "**Order matters**: The greedy choice ($10 + $5 over three $5s) ensures we handle future customers optimally"
time_complexity: "O(n). We process each customer exactly once with O(1) operations per customer."
space_complexity: "O(1). We only use two integer counters (`fives` and `tens`) regardless of input size."
solutions:
- approach_name: Greedy Simulation
is_optimal: true
code: |
def lemonade_change(bills: list[int]) -> bool:
# Track only $5 and $10 bills (we never use $20 for change)
fives = 0
tens = 0
for bill in bills:
if bill == 5:
# No change needed, just collect the $5
fives += 1
elif bill == 10:
# Need to give $5 change
if fives == 0:
return False # Can't make change
fives -= 1
tens += 1
else: # bill == 20
# Need to give $15 change
# Greedy: prefer using $10 + $5 to preserve $5 bills
if tens > 0 and fives > 0:
tens -= 1
fives -= 1
elif fives >= 3:
fives -= 3
else:
return False # Can't make change
return True # Successfully served all customers
explanation: |
**Time Complexity:** O(n) — Single pass through the bills array.
**Space Complexity:** O(1) — Only two counters used.
We simulate serving each customer, making the greedy choice to preserve $5 bills when possible. The key insight is that $10 + $5 is always preferred over three $5s for $20 change, because $5 bills are needed for both $10 and $20 transactions while $10 bills are only useful for $20 transactions.
- approach_name: Brute Force Simulation
is_optimal: false
code: |
def lemonade_change(bills: list[int]) -> bool:
# Track all bills (including $20, though unnecessary)
cash = {5: 0, 10: 0, 20: 0}
for bill in bills:
cash[bill] += 1 # Receive payment
change_needed = bill - 5
# Try to make change using largest bills first
for denomination in [20, 10, 5]:
while change_needed >= denomination and cash[denomination] > 0:
change_needed -= denomination
cash[denomination] -= 1
if change_needed > 0:
return False # Couldn't make exact change
return True
explanation: |
**Time Complexity:** O(n) — Still linear, but with more operations per customer.
**Space Complexity:** O(1) — Fixed-size dictionary.
This approach uses a general change-making algorithm: try to use the largest bills first. While it works, it's unnecessarily complex for this specific problem. It also tracks $20 bills (which are never used) and uses a loop where direct conditionals suffice. The greedy simulation above is cleaner and more efficient in practice.