Solving tips
- Reframe it: for each sell day the best buy is the cheapest price seen so far, so one left-to-right pass suffices instead of checking all pairs.
- Key insight: track min_price seen so far and the best profit = current price - min_price; update min when a lower price appears.
- Target O(n) time and O(1) space; the O(n^2) all-pairs brute force is too slow at n=1e5.
- Common pitfall: never sell before you buy (max(prices)-min(prices) is wrong), and initialize best=0 so a strictly decreasing array returns 0.
Problem
You are given an array prices where prices[i] is the price of a stock on day i. You may make at most one transaction: pick one day to buy a single share and a later day to sell it. Return the maximum profit you can achieve. If no profitable transaction exists (prices only fall), return 0 β you are allowed to skip trading entirely.
Note the ordering constraint: the sell day must come strictly after the buy day. You cannot sell first and buy later.
Examples
prices = [7, 1, 5, 3, 6, 4] β 5 β buy at 1 (day 1), sell at 6 (day 4); profit 6 - 1 = 5.
prices = [7, 6, 4, 3, 1] β 0 β prices only decline, so the best move is not to trade.
prices = [2, 4, 1, 7] β 6 β buy at 1 (day 2), sell at 7 (day 3); note the tempting 2 β 4 early pair is worse.
Constraints
1 <= len(prices) <= 10^5
0 <= prices[i] <= 10^4
With up to 10^5 days, checking every buy/sell pair (~5 * 10^9 pairs in the worst case) is too slow β the expected solution is a single pass.
Think about it first
Hint 1
For a fixed sell day, which buy day maximizes profit? You never need to consider more than one candidate.
Hint 2
Sweep left to right, keeping track of the cheapest price seen so far. At each day, the best profit selling today is today - cheapest_so_far.
Hint 3
As a two-pointer window: left is the buy day, right the sell day. If prices[right] < prices[left], no future sale ever benefits from buying at left instead of right β jump left to right. Otherwise record the profit and advance right.
TL;DR
One-pass sliding window (track the minimum buy price so far) β O(n) time, O(1) space.
Approach 1 β Brute force
Try every (buy day, sell day) pair with buy before sell and keep the best difference.
class Solution:
def maxProfit(self, prices: list[int]) -> int:
best = 0
n = len(prices)
for buy in range(n):
for sell in range(buy + 1, n):
best = max(best, prices[sell] - prices[buy])
return best
Complexity: O(nΒ²) time, O(1) space. With n = 10^5 that is on the order of 5 * 10^9 comparisons β far past the time limit.
Approach 2 β Sliding window over (buy, sell) days
The insight: if the price at the current day drops below the price at our chosen buy day, then every future sale would do better buying at the current day instead. So the buy pointer only ever jumps forward β it never pays to move it back. Both pointers move monotonically left-to-right, which is exactly the sliding-window shape.
class Solution:
def maxProfit(self, prices: list[int]) -> int:
best = 0
left = 0 # buy day
for right in range(1, len(prices)): # sell day
if prices[right] < prices[left]:
left = right # a cheaper buy day; window restarts here
else:
best = max(best, prices[right] - prices[left])
return best
Walkthrough on prices = [7, 1, 5, 3, 6, 4]:
| right | prices[right] | action | left | best |
|---|
| 1 | 1 | 1 < 7 β move buy day | 1 | 0 |
| 2 | 5 | profit 5 - 1 = 4 | 1 | 4 |
| 3 | 3 | profit 3 - 1 = 2 | 1 | 4 |
| 4 | 6 | profit 6 - 1 = 5 | 1 | 5 |
| 5 | 4 | profit 4 - 1 = 3 | 1 | 5 |
The window [left, right] grows while it stays βvalidβ (buy price is the cheapest seen) and collapses to a single day the moment a cheaper buy appears.
Complexity: O(n) time, O(1) space.
Approach 3 β Min-so-far (same idea, tighter code)
The insight: the left pointer above always sits on the minimum price seen so far, so we can store that value instead of an index.
class Solution:
def maxProfit(self, prices: list[int]) -> int:
min_price = prices[0]
best = 0
for p in prices[1:]:
if p < min_price:
min_price = p
else:
best = max(best, p - min_price)
return best
Complexity: O(n) time, O(1) space. Identical work to Approach 2 β just a different bookkeeping style.
Common pitfalls
- Selling before buying.
max(prices) - min(prices) is wrong when the maximum comes before the minimum (e.g. [9, 1, 3] β answer is 2, not 8).
- Forgetting the no-trade option. On a strictly decreasing array the answer is
0, not the least-negative loss. Initializing best = 0 handles this.
- Updating
best before updating the buy day. Compare against the min seen so far including earlier days only β the code above is safe because a new minimum canβt also be a profitable sell against itself.
Pattern takeaway
This is the degenerate-but-instructive form of a variable-size window: the window is anchored at the best candidate left endpoint, and the key argument is monotonicity β once a better left endpoint appears, the old one is dominated for every future right endpoint, so the left pointer never moves backward. That βnever look backβ argument is what makes every sliding-window algorithm linear.