The problem
You get an array prices where prices[d] is a stock’s price on day d. You may buy once and sell once, and the sale must come on a later day than the purchase. Return the largest profit you can make, or 0 if every possible trade loses money.
Input: prices = [7, 1, 5, 3, 6, 4]
Output: 5Buy on day 1 at price 1 and sell on day 4 at price 6, for a profit of 5.
How to think about it
Start with brute force. Try every buy day i and every later sell day j, and keep the largest prices[j] − prices[i]. That’s O(n²) pairs.
Flip the question around. Instead of asking “for this buy day, what’s the best sell day?”, ask “if I sell today, what’s the best day I could have bought?” The answer is obviously the cheapest day before today. So the best profit for selling today is today's price − lowest price so far.
The key insight: one number of memory is enough. You never need the whole history, only its minimum. Keep lo, the lowest price seen so far, and best, the largest gap seen so far. Each new price first updates lo, then checks whether selling today beats best. Updating lo first is fine: if today is the new minimum, the gap is 0, which never hurts.
Which pattern is this? A sliding window whose left edge jumps to the minimum: the window is “from the cheapest day so far up to today”. You’ll also see it called Kadane-style or a running-minimum scan. The tell is “best pair (i, j) with i < j, where the score is a difference”.
Approach
- Set
lo = infinityandbest = 0. - For each price
p, setlo = min(lo, p). - Set
best = max(best, p - lo). - Return
best.
Solution
def max_profit(prices):
lo = float('inf'); best = 0
for p in prices:
lo = min(lo, p); best = max(best, p - lo) # cheapest buy so far; sell today
return best
# max_profit([7,1,5,3,6,4]) -> 5Dry run
On prices = [7, 1, 5, 3, 6, 4]:
| Day | p |
lo after |
p - lo |
best after |
|---|---|---|---|---|
| 0 | 7 | 7 | 0 | 0 |
| 1 | 1 | 1 | 0 | 0 |
| 2 | 5 | 1 | 4 | 4 |
| 3 | 3 | 1 | 2 | 4 |
| 4 | 6 | 1 | 5 | 5 |
| 5 | 4 | 1 | 3 | 5 |
The answer is 5.
Complexity
- Time: O(n). One pass, constant work per day.
- Space: O(1). Two numbers, whatever the input size.
Edge cases and pitfalls
- Prices only fall (
[7, 6, 4, 3, 1]): every gap is 0, so return 0. Don’t return a negative number. - Empty or single-day input returns 0; there’s no valid trade.
- Taking
max(prices) − min(prices)is wrong when the maximum comes before the minimum, as in[5, 1]. - Updating
bestbeforeloalso works, but only because a same-day trade is worth 0. Keep the order clear in your head. - This is not the multi-trade version. If you can trade many times (LeetCode 122), you instead add every upward step.
Related problems
- Maximum Subarray (LeetCode 53): Kadane’s algorithm, the same one-pass “best ending here” idea on sums.
- Maximum Average Subarray I (LeetCode 643): a fixed-size sliding window to compare with this flexible one.
- Daily Temperatures (LeetCode 739): another “compare today with earlier days” scan, solved with a monotonic stack.
- 1.Best Time to Buy and Sell Stock (LeetCode 121)
- 2.Maximum Average Subarray I (LeetCode 643)
- 3.Longest Substring Without Repeating Characters (LeetCode 3)
- 4.Longest Repeating Character Replacement (LeetCode 424)
- 5.Permutation in String (LeetCode 567)
- 6.Find All Anagrams in a String (LeetCode 438)
- 7.Fruit Into Baskets (LeetCode 904)
- 8.Max Consecutive Ones III (LeetCode 1004)
- 9.Minimum Size Subarray Sum (LeetCode 209)
- 10.Longest Subarray of 1's After Deleting One Element (LeetCode 1493)
- 11.Sliding Window Maximum (LeetCode 239)
- 12.Minimum Window Substring (LeetCode 76)