Skip to content

121. Best Time to Buy and Sell Stock (Easy)

Problem

Given an array prices where prices[i] is the price of a stock on day i, choose a single day to buy and a later day to sell to maximize profit. Return the maximum profit you can achieve; if no profit is possible, return 0.

Example

  • prices = [7, 1, 5, 3, 6, 4]5 (buy at 1, sell at 6)
  • prices = [7, 6, 4, 3, 1]0

LeetCode 121 · Link · Easy

Approach 1: Brute force, every pair

Try all (buy_day, sell_day) with buy_day < sell_day.

def max_profit(prices: list[int]) -> int:
n = len(prices)
best = 0
for i in range(n): # L1: outer loop, n iterations
for j in range(i + 1, n): # L2: inner loop, up to n-i-1 iterations
best = max(best, prices[j] - prices[i]) # L3: O(1) arithmetic + max
return best

Where the time goes, line by line

Variables: n = len(prices).

LinePer-call costTimes executedContribution
L1 (outer loop)O(1)nO(n)
L2/L3 (inner loop)O(1)n(n-1)/2O(n²) ← dominates

Every pair (buy_day, sell_day) with buy_day < sell_day is evaluated once. There are n(n-1)/2 such pairs, giving the O(n²) total.

Complexity

  • Time: O(n²), driven by L2/L3 (the O(n²) inner loop pairs).
  • Space: O(1).

Clear, correct, slow.

Approach 2: Prefix min array

Precompute, for each day, the minimum price seen so far. Then one pass to compute the max of prices[i], min_so_far[i].

def max_profit(prices: list[int]) -> int:
n = len(prices)
if n < 2:
return 0
min_so_far = [prices[0]] * n # L1: O(n) allocation
for i in range(1, n): # L2: first pass, n-1 iterations
min_so_far[i] = min(min_so_far[i - 1], prices[i]) # L3: O(1) per step
return max(prices[i] - min_so_far[i] for i in range(n)) # L4: O(n) second pass

Where the time goes, line by line

Variables: n = len(prices).

LinePer-call costTimes executedContribution
L1 (allocate array)O(n)1O(n)
L2/L3 (prefix min pass)O(1)n-1O(n) ← tied for dominance
L4 (max profit pass)O(1)nO(n) ← tied for dominance

Two linear scans of the same array. The O(n) space for min_so_far is the only cost beyond O(1).

Complexity

  • Time: O(n), driven by L2/L3 and L4 (two linear passes).
  • Space: O(n) for the prefix array.

Approach 3: Single pass with running min (optimal)

Track the minimum price seen so far and the best profit, in one pass.

def max_profit(prices: list[int]) -> int:
lowest = float('inf') # L1: O(1)
best = 0 # L2: O(1)
for price in prices: # L3: single loop, n iterations
if price < lowest: # L4: O(1) comparison
lowest = price # L5: O(1) update buy candidate
else:
best = max(best, price - lowest) # L6: O(1) profit check
return best

Where the time goes, line by line

Variables: n = len(prices).

LinePer-call costTimes executedContribution
L1/L2 (init)O(1)1O(1)
L3 (loop)O(1) bodynO(n) ← dominates
L4/L5 (update min)O(1)at most nO(n)
L6 (profit check)O(1)at most nO(n)

One pass, two variables. L5 and L6 are mutually exclusive per iteration (either we found a cheaper buy, or we try a sell), so neither has hidden costs.

Complexity

  • Time: O(n), driven by L3 (one linear pass).
  • Space: O(1).

Why it’s a sliding window

left marks the buy day (always the minimum seen so far); right sweeps forward. When a better buy day appears, left jumps to it. Each day is visited once; the window width is variable.

Summary

ApproachTimeSpace
Every pairO(n²)O(1)
Prefix min arrayO(n)O(n)
Running min + profitO(n)O(1)

The single-pass template generalizes to many “best X after running min/max” problems (including Maximum Subarray, also known as Kadane’s).

Test cases

# Quick smoke tests - paste into a REPL or save as test_121.py and run.
# Uses the optimal Approach 3 implementation.
def max_profit(prices: list) -> int:
lowest = float('inf')
best = 0
for price in prices:
if price < lowest:
lowest = price
else:
best = max(best, price - lowest)
return best
def _run_tests():
assert max_profit([7, 1, 5, 3, 6, 4]) == 5 # buy at 1, sell at 6
assert max_profit([7, 6, 4, 3, 1]) == 0 # strictly decreasing, no profit
assert max_profit([1]) == 0 # single price, can't sell
assert max_profit([1, 2]) == 1 # two prices, simple profit
assert max_profit([2, 4, 1]) == 2 # profit before the new low
assert max_profit([3, 3, 3]) == 0 # all same price
print("all tests pass")
if __name__ == "__main__":
_run_tests()
  • Arrays, input; running-min sliding window