Problem Walkthrough

Buy and Sell Stock LeetCode 121 Deep Dive

LeetCode 121 Best Time to Buy and Sell Stock is solved in O(n) time by tracking a running minimum price as you iterate through the array. At each day you compute the profit if you sold today (current price minus the running minimum), update the maximum profit seen so far, then update the running minimum. This single-pass greedy approach — sometimes called the Kadane's-on-daily-gains equivalent — elegantly reduces an apparent O(n^2) problem to O(n) with just two variables, making it one of the clearest examples of the track-minimum greedy family.

8 min read|

Buy and Sell Stock

LeetCode 121

Problem Overview

LeetCode 121 — Best Time to Buy and Sell Stock — gives you an array prices where prices[i] is the price of a given stock on day i. You want to maximize your profit by choosing a single day to buy and a different day in the future to sell. If no profit is possible (prices are non-increasing), return 0. For example, given prices = [7, 1, 5, 3, 6, 4], the answer is 5: buy on day 2 (price = 1), sell on day 5 (price = 6). Given prices = [7, 6, 4, 3, 1], the answer is 0 because prices only decrease.

The problem imposes a strict chronological constraint: you must buy before you sell. This rules out simply finding the global minimum and global maximum and subtracting — the minimum might come after the maximum. The key challenge is efficiently finding the pair (buy_day, sell_day) with buy_day < sell_day that maximizes prices[sell_day] - prices[buy_day].

LeetCode 121 is rated Easy and appears in virtually every coding interview preparation list. It is the entry point to the Buy and Sell Stock series (LeetCode 122, 123, 188, 309, 714) which progressively adds constraints: multiple transactions, at most k transactions, cooldown periods, and transaction fees. Mastering the single-transaction greedy approach here is the foundation for all subsequent problems in the series.

  • Input: integer array prices where prices[i] is the stock price on day i
  • Output: maximum profit from one buy followed by one sell; return 0 if no profit possible
  • You must buy before you sell (buy_day < sell_day)
  • Only one transaction allowed (one buy, one sell)
  • Constraints: 1 <= prices.length <= 10^5, 0 <= prices[i] <= 10^4

Brute Force

The brute force approach checks every pair (i, j) where i < j and computes prices[j] - prices[i], tracking the maximum profit seen. Two nested loops give O(n^2) time complexity and O(1) space. For prices = [7, 1, 5, 3, 6, 4], the outer loop fixes buy day i, the inner loop scans every future sell day j, computes profit, and updates the maximum. This correctly finds the answer but runs in quadratic time.

For n up to 100,000 (the constraint on LeetCode 121), the brute force would perform up to 5 billion comparisons — far too slow. The O(n^2) approach is useful for verifying correctness of a faster solution on small inputs, but it is not acceptable as a final answer in an interview or on the OJ. The interviewer expects you to recognize that the quadratic scan is wasteful and propose a linear alternative.

The pivot to the optimal solution is this insight: when you are at sell day j, the best buy day is the minimum price in the range [0, j-1]. You do not need to re-scan [0, j-1] each time — you can maintain a running minimum as you iterate. This reduces the problem from O(n^2) to O(n) with a single extra variable.

💡

Track the minimum price seen so far and compute profit at each day — this reduces O(n^2) to O(n) with a single variable

The key insight is that at each sell day j, the best possible buy day is the global minimum of prices[0..j-1]. Instead of re-scanning [0, j-1] each time (O(n) per sell day, O(n^2) total), maintain a running minimum updated as you iterate. Then profit at day j is simply prices[j] - runningMin. Update maxProfit = max(maxProfit, prices[j] - runningMin) at each step. This converts the nested loop into a single pass with two variables: minPrice and maxProfit. The time drops from O(n^2) to O(n) and space stays at O(1).

One-Pass Greedy

The optimal solution uses a single pass with two variables: minPrice (initialized to infinity or prices[0]) and maxProfit (initialized to 0). For each price in the array, first update minPrice to be the minimum of minPrice and the current price, then update maxProfit to be the maximum of maxProfit and (current price minus minPrice). At the end, return maxProfit.

This works because at every sell day we are asking: "if I sold today, what is the best profit I could achieve?" The answer is current price minus the lowest price seen so far. By maintaining minPrice as we iterate, we answer this in O(1) per day. The maximum over all days gives the global best trade. There is no need to actually track which days correspond to the minimum and maximum — just the values.

The algorithm handles all edge cases gracefully. If prices only decrease (e.g., [7, 6, 4, 3, 1]), minPrice keeps dropping and profit is always negative or zero, so maxProfit stays at 0 — the correct answer. If there is only one price, no trade is possible and maxProfit is 0. If all prices are equal, profit is 0. Time complexity is O(n), space complexity is O(1).

  1. 1Initialize minPrice = Infinity (or prices[0]), maxProfit = 0
  2. 2For each price in prices:
  3. 3 Update minPrice = min(minPrice, price)
  4. 4 Update maxProfit = max(maxProfit, price - minPrice)
  5. 5Return maxProfit

Kadane's Algorithm Connection

There is an elegant mathematical equivalence between the buy-sell stock greedy and Kadane's algorithm on daily gains. Define the daily gain array as gains[i] = prices[i] - prices[i-1] for i from 1 to n-1. The profit from buying on day b and selling on day s equals prices[s] - prices[b] = gains[b+1] + gains[b+2] + ... + gains[s]. This is exactly the sum of a contiguous subarray of the gains array. Finding the maximum profit is therefore equivalent to finding the maximum subarray sum of the daily gains — the Maximum Subarray problem (LeetCode 53), solved by Kadane's algorithm.

Kadane's algorithm on the gains array processes each daily gain and decides: extend the current subarray or start a new one. If the running sum drops below 0, reset it to 0 (start fresh). Track the maximum running sum seen. The result is the maximum subarray sum of gains, which equals the maximum profit from buy/sell stock. Both the min-price approach and Kadane's on daily gains produce the same answer — they are mathematically equivalent reformulations of the same optimization.

Understanding both perspectives deepens your grasp of both problems. The min-price approach is more direct for LeetCode 121 and easier to explain in an interview. The Kadane connection is the key insight that links LeetCode 121 (Buy and Sell Stock) to LeetCode 53 (Maximum Subarray) — two problems that appear unrelated but share the same underlying structure. This connection frequently appears in follow-up interview questions.

ℹ️

The min-price approach and Kadane's on daily gains are mathematically equivalent — understanding both deepens your grasp of both problems

Define daily gains as prices[i] - prices[i-1]. Then: max profit from one buy/sell = max subarray sum of daily gains = Kadane's algorithm result. Conversely, the min-price tracking in LeetCode 121 is implicitly doing what Kadane's does: it accumulates gains as long as they are positive and resets when the accumulated gain would be better started fresh (i.e., when a new minimum is found). The two problems share the same O(n) time O(1) space solution structure. If you can solve one, you can derive the other. This cross-problem insight is a hallmark of strong algorithmic understanding.

Why Greedy Works

The greedy choice is: at each day, if we were to sell today, the best buy is the historical minimum. This is locally optimal and globally optimal for the single-transaction problem. The argument for correctness is straightforward: the maximum profit over the entire array equals max over all sell days s of (prices[s] - min(prices[0..s-1])). The greedy algorithm computes exactly this by maintaining the running minimum.

We never need to look ahead because we are maximizing over all possible sell days in sequence. At each sell day s, we have already seen all possible buy days (indices 0 to s-1) and we know the minimum among them. The best we can do with a sell at day s is fully determined by the running minimum, which is available in O(1). There is no future information that could change the optimal buy for day s — if a cheaper buy day existed after s, it would not be a valid buy (must come before the sell).

The greedy approach fails for the multi-transaction variants (LeetCode 122 and beyond) only because those problems allow multiple buy-sell pairs that interact. For a single transaction, the greedy is provably optimal: the two-variable scan correctly finds the global optimum without any backtracking or dynamic programming table. This is one of the cleaner greedy proofs in the LeetCode canon.

  1. 1Claim: at sell day s, the best buy is at min(prices[0..s-1])
  2. 2Proof: profit at (b, s) = prices[s] - prices[b]; maximized when prices[b] is minimized
  3. 3The running minimum captures min(prices[0..s-1]) for all s in O(1) per step
  4. 4Iterating over all s and taking the max gives the global optimum
  5. 5No future information is needed: optimal buy for day s only depends on past prices

Code Walkthrough Python and Java

Python solution: def maxProfit(prices): min_price = float('inf'); max_profit = 0; for price in prices: min_price = min(min_price, price); max_profit = max(max_profit, price - min_price); return max_profit. The loop processes each price once. float('inf') ensures the first price always updates min_price. If prices is empty (though the constraint guarantees length >= 1), max_profit returns 0 correctly. Time O(n), Space O(1).

Java solution: public int maxProfit(int[] prices) { int minPrice = Integer.MAX_VALUE; int maxProfit = 0; for (int price : prices) { minPrice = Math.min(minPrice, price); maxProfit = Math.max(maxProfit, price - minPrice); } return maxProfit; }. Integer.MAX_VALUE ensures the first price always becomes the initial minimum. The enhanced for-each loop is clean and idiomatic. Both solutions handle all-decreasing prices (maxProfit stays 0) and single-element arrays (loop runs once, profit = price - price = 0).

Common implementation mistakes to avoid: initializing minPrice to 0 instead of infinity or prices[0] — this causes incorrect results when all prices are positive (profit appears smaller than it is). Also avoid initializing maxProfit to a negative value — the problem guarantees return 0 if no profit is possible, so the floor should be 0. The two-variable pattern (minPrice, maxProfit) is the canonical O(n) O(1) solution and should be memorized for interviews.

⚠️

Initialize minPrice to prices[0] or infinity, NOT to 0 — initializing to 0 gives wrong profits when all prices are positive

A subtle but critical initialization bug: if you set minPrice = 0 and all prices are positive (e.g., prices = [3, 5, 8]), then the computed profit at each day is 3, 5, 8 respectively — as if you could buy at price 0, which is impossible. The maximum profit would be reported as 8 instead of the correct 5 (buy at 3, sell at 8). Always initialize minPrice to float("inf") in Python or Integer.MAX_VALUE in Java, or initialize it to prices[0] and start the loop from index 1. Similarly, maxProfit must start at 0 (not a negative number) because the problem returns 0 when no profitable trade exists.

Ready to master algorithm patterns?

YeetCode flashcards help you build pattern recognition through active recall and spaced repetition.

Start practicing now