Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Buy once, sell later. The interview-grade answer is a single pass that tracks the minimum price seen so far and, at each day, the best profit from selling that day = price − minSoFar. Take the max. It's a one-day-wide buy / current-day-sell sliding window. O(n) time, O(1) space, beating the O(n²) all-pairs brute force.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you recognize the buy-before-sell ordering constraint and collapse the all-pairs difference into a single pass that remembers the minimum price so far.
Where candidates slip
Returning max − min without respecting order (selling before buying), or initializing best to a value that breaks the monotonically-decreasing case.
How to stand out
State the O(n²) brute force, then 'I only need the cheapest price before today, so one pass with a running min does it.' Mention the sliding-window framing and the LC122 multi-transaction follow-up.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewMax profit, buy before sellNeed to compare every pair?Yes (naive)No (track min so far)Explore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Brute Force — Check Every Buy/Sell Pair
1Hint 1Explore hintHide hint
The answer is some pair (buy day i, sell day j) with i < j. What's the most direct way to find the best one?
2Hint 2Explore hintHide hint
Try every pair i < j and compute prices[j] − prices[i].
3Hint 3Explore hintHide hint
Track the maximum profit, never letting it go below 0. Correct but quadratic — your cue to remember the past more cheaply.
A · Core idea
Consider every valid pair of days (i, j) with i < j and compute the profit prices[j] − prices[i]. Keep the largest, flooring at 0 so that an all-declining market returns 0 rather than a negative number.
Why this is the natural first attempt. The problem is literally 'pick a buy day and a later sell day to maximize the difference', so enumerating all ordered pairs is the obvious correct baseline and makes the i < j constraint explicit.
Why we don't ship this. With n up to 10⁵, n²/2 is about 5 billion comparisons — too slow. The wasted work is re-scanning all earlier days for each sell day. But to maximize prices[j] − prices[i] for a fixed sell day j, we only need the single cheapest buy day before j — not all of them. That observation collapses the inner loop.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
prices = [7, 1, 5, 3]i=0
7→1=-6, 7→5=-2, 7→3=-4
best = 04C · Code with teaching comments
Reference implementation previewpublic int maxProfit(int[] prices) { int best = 0; int n = prices.length; for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) {Explore the full codeHide the full code
D · Why it works and complexityO(n²) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
For each sell day, only the minimum price among earlier days matters — every other earlier price gives a smaller or equal profit. Carrying that running minimum forward removes the inner loop entirely.
ComplexityO(n²) time, O(1) space — C(n,2) ≈ n²/2 ordered pairs, each O(1). Only a running max is stored → O(1) space.
E · Edge cases and pitfalls3Letting j start at i (buying and selling the same day) or before i (selling before buying).Explore edge casesHide edge cases
Pitfalls
- Letting j start at i (buying and selling the same day) or before i (selling before buying).
- Initializing best to Integer.MIN_VALUE and forgetting to clamp to 0 for the no-profit case.
- Shipping O(n²) on n = 10⁵ — it will time out.
Approach 2 of 2 · Optimal
One Pass — Min Price So Far (Optimal)
1Hint 1Explore hintHide hint
Fix the sell day as 'today' and sweep left to right. To maximize today's profit, what single fact about the past do you need?
2Hint 2Explore hintHide hint
You need the minimum price on any day at or before today. Keep it in a running variable as you scan.
3Hint 3Explore hintHide hint
At each day: update minPrice = min(minPrice, today), then candidate profit = today − minPrice, and keep the best. One pass, O(1) space.
A · Core idea
Sweep the array once, treating each day as a potential sell day. Maintain minPrice, the cheapest price seen so far (a valid buy day because it's at or before today), and best, the maximum profit found.
The mental shift from brute force. Brute force asks, for every sell day, 'what's the best earlier buy?' by re-scanning. But the best earlier buy is simply the minimum price so far — there's no reason to look at any earlier day that wasn't the cheapest. So we carry that minimum forward and ask one O(1) question per day instead of an O(n) scan.
Why update minPrice before computing profit. On each day we first refresh minPrice = min(minPrice, price). If today is a new low, the profit price − minPrice becomes 0 (sell at the same price you'd buy) rather than negative — which correctly means 'no profit from selling today.' Then we update best.
Why `best` starts at 0. If prices only fall, every price − minPrice is ≤ 0, and starting best at 0 encodes the option of not trading at all. The problem explicitly allows a 0-profit answer.
The sliding-window framing. Think of a window whose left edge is the cheapest buy day so far and whose right edge is today's sell day. As the right edge advances, the left edge jumps to any new cheaper day. The window's 'value' is the profit, and we keep the maximum — a degenerate sliding window where the left edge only moves to new minima.
Why O(1) space and one pass. Only two integers are tracked, and each price is visited exactly once.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
prices = [7, 1, 5, 3, 6, 4]Array state
p=1
new min at index 1; profit 0
5C · Code with teaching comments
D · Why it works and complexityO(n) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
The optimization is recognizing that, for a fixed sell day, only the running minimum buy price matters — so a single scalar summarizes the entire past. This 'a running extreme captures everything you need about the prefix' is the same idea that powers Kadane's algorithm for Maximum Subarray.
ComplexityO(n) time, O(1) space — A single linear scan with constant work per element → O(n) time. Two scalars (minPrice, best) → O(1) space.
E · Edge cases and pitfalls6prices = [1] — Single day; can't sell after buying, loop sets minPrice=1, profit 0 → returns 0.Explore edge casesHide edge cases
Edge cases handled
prices = [1]Single day; can't sell after buying, loop sets minPrice=1, profit 0 → returns 0.prices = [3, 2, 1]Strictly decreasing; every profit ≤ 0, best stays 0.prices = [2, 4, 1, 7]minPrice tracks 2 then 1; the 1→7 jump yields 6 as the max.
Pitfalls
- Computing profit before updating minPrice — can produce a spurious value on a new low.
- Initializing best to a negative sentinel and forgetting the no-trade 0 floor.
- Treating it as LC122 (unlimited transactions) and summing every upward step.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic int maxProfit(int[] prices) { int minPrice = Integer.MAX_VALUE; int best = 0; for (int price : prices) { minPrice = Math.min(minPrice, price);Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Ignoring the buy-before-sell order
Returning max(prices) − min(prices) is wrong when the maximum occurs before the minimum — you'd be selling before buying.
Explore the code comparisonHide the code comparison
Ignoring the buy-before-sell order
Returning max(prices) − min(prices) is wrong when the maximum occurs before the minimum — you'd be selling before buying.
Don't write this
return max(prices) - min(prices) # ignores orderingWrite this instead
min_price = float('inf')
best = 0
for price in prices:
min_price = min(min_price, price)
best = max(best, price - min_price)
return best2Profit computed before updating the min
If you compute price − minPrice before refreshing minPrice on a new low, you can record a negative or stale profit.
Explore the code comparisonHide the code comparison
Profit computed before updating the min
If you compute price − minPrice before refreshing minPrice on a new low, you can record a negative or stale profit.
Don't write this
best = Math.max(best, price - minPrice);
minPrice = Math.min(minPrice, price);Write this instead
minPrice = Math.min(minPrice, price);
best = Math.max(best, price - minPrice);Revise
Carry the pattern into the interview
Pattern to remember
One pass: track min price so far, max profit
Code pattern to memorize
minPrice = +inf
best = 0
for p in prices:
minPrice = min(minPrice, p)
best = max(best, p - minPrice)
return bestRules to remember
- 1You must buy before you sell — minPrice always refers to a day at or before the current one.
- 2Start
bestat 0: if prices only fall, no transaction beats doing nothing (profit 0). - 3Update
minPricefirst, then compute profit — selling on the same day as the min yields 0, which is fine. - 4Only one buy and one sell are allowed (LC121); multiple transactions is a different problem (LC122).
- 5Equivalent framing: a sliding window where left = cheapest-so-far buy day, right = today's sell day.
Use this pattern when
- Maximize a later-minus-earlier difference in a sequence (max gap with order constraint)
- One forward pass can carry a running min/max that summarizes the past
- Any 'best future value relative to the best so far' question
- You want to avoid an O(n²) all-pairs comparison
Choose another pattern when
- Unlimited transactions allowed → sum every upward step (LC122 greedy)
- At most k transactions / cooldown → dynamic programming with states
- No ordering constraint (sell can precede buy) → it's just max − min
Interview takeaway
Carrying the best-so-far minimum while scanning forward turns an O(n²) pair search into one pass. The 'running extreme summarizes the prefix' idea is the same engine behind Maximum Subarray (Kadane's) and many one-pass DP problems.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternOne-pass running extreme. Reach for it whenever the answer at each position depends only on the best (min/max) value seen so far. Same engine as Maximum Subarray (Kadane's) and many one-pass DP problems.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Best Time to Buy and Sell Stock IIComing soonUnlimited transactions — greedily sum every positive day-to-day increase.
- Best Time to Buy and Sell Stock IIIComing soonAt most two transactions — DP over buy1/sell1/buy2/sell2 states.
- Best Time to Buy and Sell Stock with CooldownComing soonAdd a cooldown day after selling — DP with hold/sold/rest states.
- Maximum SubarraySame 'running extreme summarizes the prefix' idea — Kadane's tracks a running sum instead of a running min.