feat(content): test cases batch 1

This commit is contained in:
2025-05-24 20:55:37 +01:00
parent 815ed3161e
commit 1e4aafaff2
4 changed files with 467 additions and 181 deletions

View File

@@ -9,81 +9,164 @@ categories:
patterns:
- greedy
function_signature: "def max_profit(prices: list[int]) -> int:"
test_cases:
visible:
- input: { prices: [7, 1, 5, 3, 6, 4] }
expected: 5
- input: { prices: [7, 6, 4, 3, 1] }
expected: 0
hidden:
- input: { prices: [1, 2, 3, 4, 5] }
expected: 4
- input: { prices: [2, 4, 1] }
expected: 2
- input: { prices: [3, 3, 3, 3] }
expected: 0
- input: { prices: [1] }
expected: 0
description: |
You are given an array `prices` where `prices[i]` is the price of a given stock on the ith day.
You are given an array `prices` where `prices[i]` is the price of a given stock on the i<sup>th</sup> day.
You want to maximize your profit by choosing a single day to buy one stock and choosing a
different day in the future to sell that stock.
You want to maximize your profit by choosing a **single day** to buy one stock and choosing a **different day in the future** to sell that stock.
Return the maximum profit you can achieve from this transaction. If you cannot achieve any
profit, return 0.
Return *the maximum profit you can achieve from this transaction*. If you cannot achieve any profit, return `0`.
constraints: |
- 1 <= prices.length <= 10^5
- 0 <= prices[i] <= 10^4
- `1 <= prices.length <= 10^5`
- `0 <= prices[i] <= 10^4`
examples:
- input: "prices = [7,1,5,3,6,4]"
output: "5"
explanation: "Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5."
explanation: "Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6 - 1 = 5. Note that buying on day 2 and selling on day 1 is not allowed because you must buy before you sell."
- input: "prices = [7,6,4,3,1]"
output: "0"
explanation: "No profitable transaction possible."
explanation: "In this case, no transactions are done and the max profit = 0."
explanation:
approach: |
1. Track the minimum price seen so far
2. For each day, calculate the profit if we sold today
3. Update maximum profit if current profit is higher
4. Update minimum price if current price is lower
intuition: |
To maximize profit, we want to buy at the lowest price and sell at the highest price after
that. Rather than comparing all pairs (O(n²)), we track the minimum price seen so far.
Imagine visualising the stock prices as a line graph over time.
For each day, we ask: "If I sold today, what's the maximum profit?" This is simply
today's price minus the minimum price we've seen before today.
Your goal is to find the **largest vertical distance** between a "valley" (lowest price) and a subsequent "peak" (highest price). The key constraint is the direction of time: you must buy *before* you sell. Therefore, you cannot simply pick the global minimum and maximum from the entire array — the minimum must appear at an earlier index than the selling price.
Think of it like this: as you walk through the days one by one, you want to keep track of the **lowest price you have seen so far**. For every new day, you ask yourself: "If I had bought at that lowest price and sold today, how much profit would I make?"
By continuously tracking this running minimum and comparing potential profits, you can find the optimal answer in a single pass.
approach: |
We solve this using a **Single Pass (Greedy) Approach**:
**Step 1: Initialise two variables**
- `min_price`: Set to infinity initially, so the first price we encounter becomes the minimum
- `max_profit`: Set to `0`, since we return `0` if no profit is possible
&nbsp;
**Step 2: Iterate through the prices array**
- For each price, check if it's lower than our current `min_price`
- If yes, update `min_price` — this ensures we're always considering buying at the lowest point found so far
- If no, calculate the potential profit: `current_price - min_price`
- Compare this with `max_profit` and keep the larger value
&nbsp;
**Step 3: Return the result**
- Return `max_profit` after checking all days
&nbsp;
This greedy approach works because at each step we make the locally optimal choice (tracking the minimum and maximum profit so far), which leads to the globally optimal solution.
common_pitfalls:
- title: Selling before buying
- title: The Brute Force Trap
description: |
Ensure you only consider selling on days after the minimum price was observed.
Tracking minimum as you iterate handles this automatically.
wrong_approach: "Using global min and max without considering order"
correct_approach: "Track running minimum, calculate profit from that point"
A common first instinct is to use nested loops to compare every pair of days:
- Outer loop `i` from `0` to `n-1` (buy day)
- Inner loop `j` from `i+1` to `n-1` (sell day)
- title: Initializing min_price to 0
This results in **O(n²) time complexity**. With the constraint `prices.length <= 10^5`, an O(n²) solution means up to 10 billion operations — this will cause a **Time Limit Exceeded (TLE)** error.
wrong_approach: "Nested loops comparing all pairs"
correct_approach: "Single pass tracking running minimum"
- title: Buying After Selling
description: |
Initialize min_price to the first element or infinity, not 0, since prices are positive
and you need to track actual minimum.
Don't find the global minimum and global maximum independently without considering their order.
For example, with `prices = [5, 4, 3, 2, 1]`, the minimum is `1` (last day) and maximum is `5` (first day). But you can't sell on day 1 and buy on day 5 — time only moves forward!
By tracking `min_price` as we iterate left-to-right, we automatically ensure we only consider selling *after* buying.
wrong_approach: "min(prices) and max(prices) separately"
correct_approach: "Track running minimum while iterating forward"
- title: Forgetting the No-Profit Case
description: |
When prices strictly decrease (e.g., `[7, 6, 4, 3, 1]`), no profitable transaction exists. Your algorithm should return `0`, not a negative number.
Initialising `max_profit = 0` handles this automatically — we only update it when we find a positive profit.
key_takeaways:
- Single pass through array is sufficient
- Track running minimum for optimal buy point
- Greedy approach works when you can only make one transaction
- This is a foundation for more complex stock problems
- "**Greedy pattern**: Make locally optimal choices (update min/max) at each step to find the global optimum"
- "**One-pass efficiency**: When looking for maximum differences in sequential data, try maintaining running variables rather than re-scanning"
- "**Foundation for harder problems**: This logic extends to variants like multiple transactions, cooldown periods, or transaction fees"
- "**Time-space tradeoff**: We sacrifice no space (O(1)) and gain optimal time (O(n)) by being clever about what we track"
time_complexity: "O(n)"
space_complexity: "O(1)"
complexity_explanation: |
Time: Single pass through the prices array.
Space: Only two variables needed (min_price, max_profit).
time_complexity: "O(n). We traverse the list of prices exactly once."
space_complexity: "O(1). We only use two variables (`min_price` and `max_profit`), regardless of the input size."
solutions:
- approach_name: Single Pass (Optimal)
- approach_name: Single Pass (Greedy)
is_optimal: true
code: |
def max_profit(prices: list[int]) -> int:
# Start with infinity so first price becomes minimum
min_price = float('inf')
# Start with 0 since that's our answer if no profit possible
max_profit = 0
for price in prices:
# Found a new lowest price? Update our buy point
if price < min_price:
min_price = price
elif price - min_price > max_profit:
max_profit = price - min_price
# Calculate profit if we sold today at this price
else:
current_profit = price - min_price
# Keep track of the best profit we've seen
if current_profit > max_profit:
max_profit = current_profit
return max_profit
explanation: |
Track minimum price seen so far and maximum profit achievable.
Update both as we iterate through prices.
**Time Complexity:** O(n) — Single pass through the array.
**Space Complexity:** O(1) — Only two variables used.
We iterate once, updating our minimum buy price and maximum achievable profit as we go. The greedy choice at each step (buy at lowest seen, sell at current if profitable) guarantees the optimal solution.
- approach_name: Brute Force
is_optimal: false
code: |
def max_profit(prices: list[int]) -> int:
max_profit = 0
n = len(prices)
# Try every possible buy day
for i in range(n):
# Try every possible sell day after buy day
for j in range(i + 1, n):
profit = prices[j] - prices[i]
max_profit = max(max_profit, profit)
return max_profit
explanation: |
**Time Complexity:** O(n²) — Nested loops comparing all pairs.
**Space Complexity:** O(1) — Only tracking max_profit.
This approach checks every valid (buy, sell) pair. While correct, it's too slow for large inputs and will result in TLE on LeetCode. Included here to illustrate why the greedy approach is necessary.