Best Time to Buy and Sell Stock in Java DSA: Brute Force to the One-Pass Scan

  • Last Updated: July 24, 2026
  • By: javahandson
  • Series
img

Best Time to Buy and Sell Stock in Java DSA: Brute Force to the One-Pass Scan

Best Time to Buy and Sell Stock in Java DSA explained for beginners. Brute force to one-pass scan with dry runs, clean code, and complexity comparison.

1. Introduction

Best Time to Buy and Sell Stock in Java is the second problem in this series. It follows Two Sum, and it teaches a different habit. Here you stop comparing pairs and start remembering one useful fact.

The story is simple. You get the daily price of a stock. You may buy on one day and sell on a later day. Your job is to find the largest profit you can make.

You get one trade only. Buy once, sell once, and you are done. Also, the sell day must come after the buy day, so time only moves forward.

We build the answer in three steps. Brute force checks every buy and sell pair. A cleaner version tracks the best price behind us. The final one-pass scan does the whole job in a single sweep.

Every approach here comes with a full dry run. We trace the same six prices through each one, so you can watch fifteen comparisons shrink down to six easy steps.

2. Understanding the Problem

Let us fix the rules before we write any code. A muddy problem statement is where most bugs are born.

  • You get an array of prices, for example [7, 1, 5, 3, 6, 4].
  • Each position in that array stands for one day. Index 0 is day 0.
  • You pick one day to buy and a later day to sell.
  • Your profit is the sell price minus the buy price.
  • Return the biggest profit possible. If no trade earns money, return 0.

For our prices the answer is 5. You buy on day 1 at price 1, then sell on day 4 at price 6. That gives 6 minus 1, which is 5.

Notice we return the profit, not the two days. Beginners often return the indices out of habit from Two Sum. Read the required output carefully.

2.1 Why Order Matters So Much

The lowest price in our array is 1 and the highest is 7. Yet the answer is not 7 minus 1.

  • Price 7 sits on day 0, which comes before price 1 on day 1.
  • So you would have to sell before you bought, and that is not allowed.
  • The real pair must have the buy day sitting to the left of the sell day.

That one rule shapes every solution below. Any approach you write has to respect the direction of time.

2.2 Why This Example Array

Our six prices are chosen to be a little awkward. That makes the traces honest.

  • Day 0 holds 7, the largest value, sitting in the worst possible spot.
  • Day 1 holds 1, the lowest value, and it arrives early enough to be useful.
  • Prices rise and fall after that, so a lazy rule like “sell at the end” fails.
  • The last price is 4, which is not the peak, so the answer never hides at the edge.

A tidy rising array like [1, 2, 3] hides all of this. A bumpy one shows you what the loop really does.

💡 Interview Insight
Interviewers love to ask what happens when prices only fall, such as [7, 6, 4, 3, 1]. The answer there is 0, because you simply do not trade. Saying that out loud early shows you thought about edge cases before coding.

3. Concepts You Need Here

Two small ideas carry this whole problem. Both show up again in later array questions, so it pays to learn them properly.

3.1 Profit Is Just a Subtraction

Say you buy at 1 and sell at 6. Your profit is 6 minus 1, which is 5. That is the entire formula.

profit = sellPrice - buyPrice
 
buy = 1, sell = 6   ->  profit = 5
buy = 7, sell = 4   ->  profit = -3   (a loss)
buy = 3, sell = 3   ->  profit = 0    (no gain)

Look at the second line. Selling lower than you bought gives a negative number. We never accept those, because doing nothing always beats losing money.

So our answer starts at 0 and only ever moves up. A negative profit gets ignored, which is exactly what “do not trade” means.

3.2 Running Minimum

Here is the second idea, and it is the one that makes this problem click.

Walk through the prices from left to right. At every day, ask one question. What is the cheapest price I have seen so far?

That value has a name. We call it the running minimum, and it never grows. It either stays the same or drops.

  • On day 0 the cheapest price seen is 7, because nothing came before it.
  • By day 1 the cheapest price drops to 1, since 1 beats 7.
  • From day 2 onward it stays at 1, because no later price goes lower.

Why does that help? Because the cheapest price so far is always a legal buy day. It sits behind you, so selling today is allowed.

3.3 Running Maximum

We track one more value alongside the minimum. It holds the best profit found so far.

This one works the opposite way. It never shrinks, and it only climbs when today beats the old record.

maxProfit = max(maxProfit, todayPrice - minPrice)
 
old best = 4, today gives 5  ->  new best = 5
old best = 5, today gives 2  ->  best stays 5

Keeping a running best is a habit you will use constantly. Maximum Subarray uses it, and so do dozens of other problems.

4. Approach 1: Brute Force

The first idea most people reach for feels obvious. Try every buy day with every later sell day. Keep the best profit you find.

It is slow, but it is honest. Nothing gets missed, because nothing gets skipped.

4.1 Pseudocode

best = 0
 
for i from 0 to n-1:          // the buy day
    for j from i+1 to n-1:    // the sell day
        profit = prices[j] - prices[i]
        if profit > best:
            best = profit
 
return best

4.2 Reading the Pseudocode

Picture two fingers on the array again. Your left finger picks the buy day. Your right finger slides across every later day looking for a good sale.

  • The outer loop moves the buy day, so i holds the price you pay.
  • The inner loop moves the sell day, so j visits every position after i.
  • We start j at i plus 1 because you cannot sell on the day you buy.
  • Each pair gives a profit, and we keep it only when it beats the record.

Why does best start at 0 rather than something tiny? Because a losing trade is never worth taking. Starting at 0 quietly encodes the rule “do nothing if nothing pays”.

Also notice there is no early return here. A big profit might still be waiting three days ahead, so the loops must finish.

4.3 Java Code

public class StockBrute {
 
    public static int maxProfit(int[] prices) {
        int best = 0;
 
        for (int i = 0; i < prices.length; i++) {
            for (int j = i + 1; j < prices.length; j++) {
                int profit = prices[j] - prices[i];
                if (profit > best) {
                    best = profit;
                }
            }
        }
        return best;
    }
 
    public static void main(String[] args) {
        int[] prices = { 7, 1, 5, 3, 6, 4 };
        System.out.println(maxProfit(prices)); // 5
    }
}

4.4 Reading the Java Code

Each line here does one small job. Let us walk down them slowly.

  • Line 4 sets best to 0, which doubles as our answer when no trade pays.
  • The outer loop on line 6 walks i from day 0 to the final day.
  • Line 7 starts the inner loop, and j always begins one step ahead of i.
  • Profit for this exact pair of days gets computed on line 8.
  • Line 9 compares that profit against the record we are holding.
  • Only a bigger profit reaches line 10, where best gets updated.
  • Line 15 hands back the record after both loops have finished.

One detail deserves a second look. The inner loop shrinks as i moves right.

  • With i at 0, j covers days 1 through 5, giving five checks.
  • Once i reaches 1, j covers days 2 through 5, giving four checks.
  • By the time i hits 4, only one check remains.

Add those up and you get fifteen checks for six prices. That number grows fast, and it is the weakness we fix next.

4.5 Dry Run of the Brute Force

Let us trace prices = [7, 1, 5, 3, 6, 4]. Watch both pointers move and watch best climb.

Stepi (buy day)prices[i]j (sell day)prices[j]Profitbest after
10711-60
20725-20
30733-40
40746-10
50754-30
6112544
7113324
8114655
9115435
102533-25
11254615
122554-15
13334635
14335415
154654-25

4.6 Reading the Dry Run

Fifteen steps ran, and only two of them changed anything. Let us break that into phases.

  • Steps 1 to 5 buy at 7, the worst possible price. Every profit comes out negative, so best never moves.
  • Step 6 buys at 1 and sells at 5, giving a profit of 4. That is our first real record.
  • Step 8 buys at 1 and sells at 6, giving 5. The record improves one last time.
  • Steps 9 to 15 keep testing pairs, and none of them beats 5.

Now look at the wasted effort. Thirteen of the fifteen steps produced nothing at all.

  • The five steps buying at 7 were doomed from the start, since 7 is the highest price.
  • Steps 10 to 15 buy at 5, 3 and 6, all of which cost more than the 1 already behind them.
  • The code never noticed that a cheaper buy day was sitting there the whole time.

That last point is the key. Once you have seen a price of 1, buying at 5 makes no sense. The brute force keeps trying anyway.

4.7 Time and Space Cost

Brute force tests every valid pair of days. For n prices that is about n times n over two checks.

  • Time is O(n squared), because one loop sits inside the other.
  • Space is O(1), since we only hold a few numbers.

Six prices needed fifteen steps. Twelve prices would need sixty six. A year of daily prices would need over sixty thousand.

💡 Interview Insight
A common follow-up is whether you can break out of the loops early. You cannot here, because a bigger profit may still be waiting. Explaining why an early exit is unsafe scores as well as writing the optimal code.

5. Approach 2: Track the Cheapest Day So Far

The dry run above handed us the fix. Buying at 5 was pointless once a price of 1 sat behind us.

So let us keep only the cheapest price we have seen. Then, on each day, we pretend we bought on that cheapest day and sell today.

This version still uses two variables and one loop. We write it in a slightly long form first, because the compact version hides what is happening.

5.1 Pseudocode

minPrice = prices[0]
best = 0
 
for i from 1 to n-1:
    todayProfit = prices[i] - minPrice
 
    if todayProfit > best:
        best = todayProfit
 
    if prices[i] < minPrice:
        minPrice = prices[i]
 
return best

5.2 Reading the Pseudocode

One loop now, and two jobs inside it. Read the order of those jobs carefully.

  • We seed minPrice with the very first price, since day 0 is the only day seen at the start.
  • The loop then starts from day 1, because selling on day 0 is impossible.
  • First we compute the profit from selling today at the cheapest price behind us.
  • Then we check whether today is itself a new cheapest price.

Why compute the profit before updating minPrice? Because you cannot buy and sell on the same day. If today became the new minimum first, the profit would come out as zero and hide a real answer.

There is a nice safety net built in here. When today is the cheapest price, todayProfit turns negative, so best simply refuses it.

5.3 Java Code

public class StockTrackMin {
 
    public static int maxProfit(int[] prices) {
        if (prices.length == 0) {
            return 0;
        }
 
        int minPrice = prices[0];
        int best = 0;
 
        for (int i = 1; i < prices.length; i++) {
            int todayProfit = prices[i] - minPrice;
 
            if (todayProfit > best) {
                best = todayProfit;
            }
 
            if (prices[i] < minPrice) {
                minPrice = prices[i];
            }
        }
        return best;
    }
 
    public static void main(String[] args) {
        int[] prices = { 7, 1, 5, 3, 6, 4 };
        System.out.println(maxProfit(prices)); // 5
    }
}

5.4 Reading the Java Code

The shape is simple, so let us focus on why each piece exists.

  • Line 4 guards against an empty array, which would crash on the next line.
  • On line 8 we seed minPrice with the first price, not a made-up huge number.
  • Line 9 seeds best at 0, encoding our “do nothing” fallback.
  • The loop on line 11 starts at index 1, skipping the day we already used as the buy.
  • Line 12 asks what we would earn by selling today at the cheapest price so far.
  • Lines 14 to 16 raise the record only when today beats it.
  • Lines 18 to 20 lower minPrice only when today is cheaper.

Compare this with the brute force. The inner loop is gone completely, replaced by a single subtraction.

  • Brute force asked, what is the best sell day for this buy day?
  • This version flips it and asks, what is the best buy day for this sell day?
  • That flipped question has a one-word answer, and the running minimum already holds it.

5.5 Dry Run of the Track-Minimum Approach

Same six prices. Watch minPrice drop once, then stay put.

Stepi (today)prices[i]minPrice beforetodayProfitbest afterminPrice after
11171 – 7 = -601
22515 – 1 = 441
33313 – 1 = 241
44616 – 1 = 551
55414 – 1 = 351

5.6 Reading the Dry Run

Five steps instead of fifteen. Let us look at what happened in each one.

  • Step 1 sells at 1 against a minimum of 7, giving -6. That loss is rejected, and 1 becomes the new minimum.
  • Step 2 sells at 5 against a minimum of 1, giving 4. Our first real profit lands.
  • Step 3 sells at 3, giving only 2, so the record stays at 4.
  • Then day 4 sells at 6, giving 5. The record climbs one final time.
  • Step 5 sells at 4, giving 3, which changes nothing.

Step 1 is the interesting one. The profit was negative, yet that step still did useful work.

  • It rejected a losing trade, which kept best sitting safely at 0.
  • It also recorded 1 as the new cheapest price.
  • Every later step then benefited from that recorded value.

Notice also that minPrice never rose. Once 1 was found on day 1, it stayed for the rest of the run. That is what a running minimum does.

💡 Interview Insight
Expect a question about seeding minPrice with Integer.MAX_VALUE instead of prices[0]. Both work, and MAX_VALUE lets you start the loop at index 0 and drop the empty-array guard. Mention that you picked prices[0] for readability, and the trade-off shows judgement.

6. Approach 3: The One-Pass Scan

The version above is already O(n). So what is left to improve?

Nothing about speed, honestly. What changes is the shape of the code. Interviewers expect this tighter form, and it is worth being able to write it without thinking.

The idea stays identical. We just express both updates with Math.min and Math.max, and we start the loop at day 0.

6.1 Pseudocode

minPrice = a very large number
best = 0
 
for each price in prices:
    minPrice = min(minPrice, price)
    best = max(best, price - minPrice)
 
return best

6.2 Reading the Pseudocode

Four lines of logic now. But one detail flipped, and it trips people up.

  • We start minPrice at a huge value, so the very first real price always replaces it.
  • Inside the loop we update minPrice first, before computing the profit.
  • Then we compare today price minus minPrice against the running best.

Hold on. Did we not just say the opposite in section 5.2? We did, and the difference is worth understanding.

When today is the cheapest price, minPrice becomes today price. The profit then works out as today minus today, which is 0. Since best already sits at 0 or higher, that zero changes nothing.

So updating first is safe here, but only because best can never drop below 0. The two versions agree on every input.

6.3 Java Code

public class StockOnePass {
 
    public static int maxProfit(int[] prices) {
        int minPrice = Integer.MAX_VALUE;
        int best = 0;
 
        for (int price : prices) {
            minPrice = Math.min(minPrice, price);
            best = Math.max(best, price - minPrice);
        }
        return best;
    }
 
    public static void main(String[] args) {
        int[] prices = { 7, 1, 5, 3, 6, 4 };
        System.out.println(maxProfit(prices)); // 5
    }
}

6.4 Reading the Java Code Line by Line

This version says the most in the fewest lines, so let us go gently.

  • Line 4 sets minPrice to the largest int Java can hold, roughly 2.1 billion.
  • Any real stock price beats that, so the first Math.min call replaces it immediately.
  • Line 5 sets best to 0, our floor and our fallback answer.
  • An enhanced for loop appears on line 7, since we never need the index here.
  • Line 8 keeps minPrice at the cheapest value seen up to and including today.
  • Our record is compared against today on line 9, and the larger number survives.
  • Line 11 returns the record once the sweep finishes.

Two things make this version pleasant to write under pressure.

  • There is no index arithmetic, so off-by-one mistakes disappear.
  • There is no empty-array guard, because an empty loop simply returns 0.

Set the two versions side by side and the equivalence becomes clear.

// Section 5 version (update min last)
int todayProfit = prices[i] - minPrice;
if (todayProfit > best)   best = todayProfit;
if (prices[i] < minPrice) minPrice = prices[i];
 
// Section 6 version (update min first)
minPrice = Math.min(minPrice, price);
best     = Math.max(best, price - minPrice);

Both compute the same answer on every input. The second is shorter, and the first is easier to explain out loud.

6.5 Dry Run of the One-Pass Scan

One final trace through the same prices. Watch minPrice and best evolve together.

StepDaypriceminPrice beforeminPrice afterprice – minPricebest after
107MAX_VALUE77 – 7 = 00
211711 – 1 = 00
325115 – 1 = 44
433113 – 1 = 24
546116 – 1 = 55
654114 – 1 = 35

6.6 Reading the Dry Run

Six steps, one per day, and no nesting anywhere. Here is what each day did.

  • Step 1 meets price 7 and drops minPrice from MAX_VALUE straight down to 7. The profit is 0, so best stays at 0.
  • Step 2 meets price 1, a new low. Again the profit is 0, because we bought and sold on the same day.
  • Step 3 meets price 5 and finally earns something. Selling at 5 against a buy of 1 gives 4.
  • Step 4 meets price 3 and earns only 2, so the record holds.
  • Then day 4 arrives with price 6 and earns 5, our best trade of the run.
  • Step 6 meets price 4 and earns 3, which changes nothing.

Steps 1 and 2 are worth studying. Both produced a profit of exactly 0.

  • That happens whenever the current price is also the new minimum.
  • The subtraction becomes price minus itself, which is always 0.
  • Since best starts at 0, those steps quietly do nothing to the answer.
  • They still matter though, because they set up the minimum for later days.

There is a quiet promise hidden in this loop. Every possible trade gets considered exactly once, always from the sell side looking back at the cheapest buy. You never miss a pair, and you never check one twice.

6.7 Comparing the Three Traces

Same prices, same answer, three very different amounts of work.

ApproachSteps takenTimes best changedExtra memory
Brute force15 pair checks2 (at steps 6 and 8)None
Track minimum5 day steps2 (at steps 2 and 4)Two int values
One-pass scan6 day steps2 (at steps 3 and 5)Two int values

The gap grows quickly with size. At six prices it looks small. At six thousand it is the difference between instant and unusable.

💡 Interview Insight
A classic follow-up asks how you would also report the buy and sell days, not just the profit. Keep two extra variables that record the index of minPrice and the index where best last improved. Answering that smoothly shows you understand the loop rather than memorising it.

7. The Dry Run on Paper

Tables are exact, but a sketch often lands faster. Here is the same one-pass trace drawn by hand.

Pen and paper dry run of the Best Time to Buy and Sell Stock one-pass scan in Java

The prices sit along the top with their day numbers. Each block on the left shows the day being visited and the question being asked. The panel on the right shows minPrice and maxProfit after that day.

Follow the colour cues as you read it.

  • A gold box marks day 1, where the cheapest price of 1 gets recorded.
  • A green box marks day 4, where selling at 6 produces the winning profit.
  • Green panels on the right mark the two moments when maxProfit actually improved.

Some people read pictures faster than tables. Use whichever version clicks, or sketch your own on paper while you follow the code.

8. Comparing the Three Approaches

All three return the correct answer. They just pay very different prices for it.

ApproachTimeSpaceNote
Brute forceO(n squared)O(1)Easy to write, unusable on long price histories
Track minimumO(n)O(1)Fast and very easy to explain out loud
One-pass scanO(n)O(1)Same speed, tighter code, the expected answer

Notice that space stays at O(1) across all three. Unlike Two Sum, this problem needs no map at all. You only carry two numbers no matter how long the array grows.

In an interview, start with brute force to prove you understand the rules. Then explain the wasted work you spotted in the trace. Finally tighten it into the one-pass scan.

That climb from slow to fast is the story interviewers came to hear. Jumping straight to the optimal answer skips the reasoning they are trying to observe.

💡 Interview Insight
If you are asked to handle many trades instead of one, the problem becomes Best Time to Buy and Sell Stock II. There you add up every upward step, which is a much simpler loop. Knowing the family of variants is worth more than memorising one solution.

9. Common Mistakes and Edge Cases

A handful of small traps catch beginners on this one. Keep them in mind.

  • Returning the highest price minus the lowest price is the most common slip. Order matters, so the low must come first.
  • Starting best at Integer.MIN_VALUE lets a losing trade become the answer. Zero is the correct floor.
  • Updating minPrice before computing the profit is fine, but only when best cannot go below zero.
  • A falling array like [7, 6, 4, 3, 1] must return 0, not a negative number.
  • An array with one price also returns 0, since a sell day never exists.
  • An empty array should return 0 too, and the one-pass version handles it for free.

Run those last three cases through your code before you say you are finished. They catch more bugs than any normal input will.

10. Conclusion

Best Time to Buy and Sell Stock in Java looks like a warm-up, and in a way it is. But the running minimum pattern inside it is a real skill.

Our six-price trace showed the payoff plainly. Brute force needed fifteen comparisons. The one-pass scan needed six simple steps and no extra memory at all.

So take the pattern, not just the answer. Write brute force first when you feel stuck. Then read your own dry run and hunt for repeated work.

That habit is what turned fifteen steps into six here. It will do the same for Maximum Subarray, for sliding window problems, and for plenty more waiting further down the list.

11. Further Reading

12. Interview Questions

Q: What is the time complexity of Best Time to Buy and Sell Stock in Java?

A: The optimal one-pass scan runs in O(n) time and O(1) space. It visits each price once and carries only two integers, minPrice and maxProfit. The brute force version runs in O(n squared) because it nests one loop inside another.

Q: Why can I not just subtract the lowest price from the highest price?

A: Because order matters. You must buy before you sell. In the array [7, 1, 5, 3, 6, 4] the highest price 7 sits on day 0, before the lowest price 1 on day 1. Selling before buying is not allowed, so the answer is 5 rather than 6.

Q: What should the answer be when prices only go down?

A: The answer is 0. For an input like [7, 6, 4, 3, 1] every possible trade loses money, so you simply do not trade. This is why maxProfit starts at 0 rather than at Integer.MIN_VALUE.

Q: Should I update minPrice before or after calculating the profit?

A: Both orders give the same answer. If you update minPrice first, then on a new low the profit becomes price minus itself, which is 0 and cannot lower a maxProfit that already sits at 0 or above. If you update it last, the profit on a new low comes out negative and gets rejected the same way.

Q: How do I also return the buy and sell days instead of just the profit?

A: Keep two extra variables. Store the index whenever minPrice changes, and store both that index and the current index whenever maxProfit improves. This adds a few lines but does not change the O(n) time or O(1) space.

Q: How is this different from Best Time to Buy and Sell Stock II?

A: This problem allows exactly one trade. The second version allows unlimited trades, so you simply add up every upward step between consecutive days. That variant is actually easier to code once you understand this one.

javahandson.com | DSA Series | Arrays & Strings

Leave a Comment