LeetCode 121 — Best Time to Buy and Sell Stock I | Two Pointer / Sliding Window Explained
LeetCode 121 — Best Time to Buy and Sell Stock I | Two Pointer / Sliding Window Explained

LeetCode 121 — Best Time to Buy and Sell Stock I | Two Pointer / Sliding Window Explained

A beginner-friendly breakdown of the classic stock profit problem — intuition, dry run, commented Java code, and complexity analysis.

20 views
0
0

🚀 Try This Problem First!

Before reading the solution, attempt it yourself on LeetCode — you'll retain the concept far better.

🔗 Problem Link: https://leetcode.com/problems/best-time-to-buy-and-sell-stock/


Understanding the Problem

You are given an array prices where prices[i] is the stock price on day i. You can buy on one day and sell on a later day. You want to maximize your profit.

Goal: Return the maximum profit possible. If no profit can be made, return 0.

Key Rules:

  1. You must buy before you sell — no going back in time.
  2. You can only make one transaction (one buy, one sell).
  3. If prices only go down, profit is impossible — return 0.

Constraints:

  1. 1 ≤ prices.length ≤ 10⁵
  2. 0 ≤ prices[i] ≤ 10⁴


Understanding the Problem With a Real-World Analogy

Imagine you're watching a stock ticker for a week. You want to pick the single best day to buy and a later day to sell. You can't predict the future — you just have historical prices. The question is: looking back at all the prices, what was the best single buy-sell pair you could have chosen?


Key Observation (Before Writing a Single Line of Code)

The profit on any given pair of days is:

profit = prices[sell_day] - prices[buy_day]

To maximize profit, for every potential sell day, we want the lowest possible buy price seen so far to the left of it. This is the core insight that drives the efficient solution.

If at any point the current price is lower than our tracked minimum, there is no point keeping the old minimum — the new one is strictly better for any future sell day.


Intuition — The Two Pointer Approach

We use two pointers i (buy pointer) and j (sell pointer), both starting at the beginning with i = 0 and j = 1.

At every step we ask: is prices[j] greater than prices[i]?

If yes → this is a profitable pair. Compute the profit and update the maximum if it's better.

If noprices[j] is cheaper than prices[i]. There's no reason to keep i where it is — any future sell day would be better served by buying at j instead. So we move i to j.

Either way, j always moves forward by 1 until it reaches the end of the array.


Why Moving i to j is Correct

This is the most important conceptual point. When prices[j] < prices[i], you might wonder — why not just skip j and move on? Why move i?

Because for any future day k > j, the profit buying at j is:

prices[k] - prices[j]

And the profit buying at i is:

prices[k] - prices[i]

Since prices[j] < prices[i], buying at j will always give a higher or equal profit for the same sell day k. So we update i = j — there is no scenario where keeping the old i is better.


Dry Run — Example 1 (Step by Step)

Input: prices = [7, 1, 5, 3, 6, 4]

We start with i = 0, j = 1, maxP = 0.

Step 1: i = 0, j = 1 → prices[i] = 7, prices[j] = 1

7 > 1 → prices[j] is cheaper. Move i to j. i = 1, j moves to 2. maxP = 0.

Step 2: i = 1, j = 2 → prices[i] = 1, prices[j] = 5

1 < 5 → Profitable! profit = 5 - 1 = 4. 4 > 0 → update maxP = 4. j moves to 3.

Step 3: i = 1, j = 3 → prices[i] = 1, prices[j] = 3

1 < 3 → Profitable! profit = 3 - 1 = 2. 2 < 4 → maxP stays 4. j moves to 4.

Step 4: i = 1, j = 4 → prices[i] = 1, prices[j] = 6

1 < 6 → Profitable! profit = 6 - 1 = 5. 5 > 4 → update maxP = 5. j moves to 5.

Step 5: i = 1, j = 5 → prices[i] = 1, prices[j] = 4

1 < 4 → Profitable! profit = 4 - 1 = 3. 3 < 5 → maxP stays 5. j moves to 6. j = 6 = prices.length → loop ends.

Output: maxP = 5 ✅ (Buy at day 2 price=1, sell at day 5 price=6)


Dry Run — Example 2 (Step by Step)

Input: prices = [7, 6, 4, 3, 1]

We start with i = 0, j = 1, maxP = 0.

Step 1: i = 0, j = 1 → prices[i] = 7, prices[j] = 6

7 > 6 → Move i to j. i = 1, j moves to 2. maxP = 0.

Step 2: i = 1, j = 2 → prices[i] = 6, prices[j] = 4

6 > 4 → Move i to j. i = 2, j moves to 3. maxP = 0.

Step 3: i = 2, j = 3 → prices[i] = 4, prices[j] = 3

4 > 3 → Move i to j. i = 3, j moves to 4. maxP = 0.

Step 4: i = 3, j = 4 → prices[i] = 3, prices[j] = 1

3 > 1 → Move i to j. i = 4, j moves to 5. j = 5 = prices.length → loop ends.

Output: maxP = 0 ✅ (Prices only went down, no profitable transaction exists)


The Code — Implementation

class Solution {
public int maxProfit(int[] prices) {
int i = 0; // Buy pointer — points to the current minimum price day
int j = 1; // Sell pointer — scans forward through the array
int maxP = 0; // Tracks the maximum profit seen so far

// j scans from day 1 to the end of the array
while (i < j && j < prices.length) {

if (prices[i] > prices[j]) {
// prices[j] is cheaper than current buy price
// Move buy pointer to j — buying here is strictly better
// for any future sell day
i = j;
} else {
// prices[j] > prices[i] — this is a profitable pair
// Calculate profit and update maxP if it's the best so far
int profit = prices[j] - prices[i];
if (profit > maxP) {
maxP = profit;
}
}

// Always move the sell pointer forward
j++;
}

// If no profitable transaction was found, maxP remains 0
return maxP;
}
}


Code Walkthrough — Step by Step

Initialization: i = 0 acts as our buy day pointer (always pointing to the lowest price seen so far). j = 1 is our sell day pointer scanning forward. maxP = 0 is our answer — defaulting to 0 handles the case where no profit is possible.

The loop condition: i < j ensures we never sell before buying. j < prices.length ensures we don't go out of bounds.

When prices[i] > prices[j]: The current sell day price is cheaper than our buy day. We update i = j, because buying at j dominates buying at i for all future sell days.

When prices[j] >= prices[i]: We have a potential profit. We compute it and update maxP if it beats the current best.

j always increments: Regardless of which branch we take, j++ moves the sell pointer forward every iteration — this is what drives the loop to completion.

Common Mistakes to Avoid

Not returning 0 for no-profit cases: If prices are strictly decreasing, maxP never gets updated and stays 0. The initialization maxP = 0 handles this correctly — never initialize it to a negative number.

Using a nested loop (brute force): A common first instinct is two nested loops checking every pair. That is O(N²) and will TLE on large inputs. The two pointer approach solves it in O(N).

Thinking you need to sort: Sorting destroys the order of days, which is fundamental to the problem. Never sort the prices array here.

Moving i forward by 1 instead of to j: When prices[j] < prices[i], some people write i++ instead of i = j. This is wrong — you might miss the new minimum entirely and waste iterations.


Complexity Analysis

Time Complexity: O(N) The j pointer traverses the array exactly once from index 1 to the end. The i pointer only moves forward and never exceeds j. So the entire algorithm is a single linear pass — O(N).

Space Complexity: O(1) No extra arrays, maps, or stacks are used. Only three integer variables — i, j, and maxP.


Alternative Way to Think About It (Min Tracking)

Some people write this problem using a minPrice variable instead of two pointers. Both approaches are equivalent — the two pointer framing is slightly more intuitive visually, but here is the min-tracking version for reference:


int minPrice = Integer.MAX_VALUE;
int maxProfit = 0;
for (int price : prices) {
if (price < minPrice) {
minPrice = price;
} else if (price - minPrice > maxProfit) {
maxProfit = price - minPrice;
}
}
return maxProfit;

The logic is the same — always track the cheapest buy day seen so far, and for each day compute what profit would look like if you sold today.


Similar Problems (Build on This Foundation)

  1. LeetCode 122 — Best Time to Buy and Sell Stock II (multiple transactions allowed) [ Blog is also avaliable on this - Read Now ]
  2. LeetCode 123 — Best Time to Buy and Sell Stock III (at most 2 transactions) [ Blog is also avaliable on this - Read Now ]
  3. LeetCode 188 — Best Time to Buy and Sell Stock IV (at most k transactions)
  4. LeetCode 309 — Best Time to Buy and Sell Stock with Cooldown
  5. LeetCode 714 — Best Time to Buy and Sell Stock with Transaction Fee

LeetCode 121 is the foundation. Master this one deeply before moving to the others — they all build on the same core idea.


Key Takeaways

✅ For every potential sell day, the best buy day is always the minimum price seen to its left — this drives the whole approach.

✅ When the sell pointer finds a cheaper price than the buy pointer, always move the buy pointer there — it strictly dominates the old buy day for all future sells.

✅ Initialize maxP = 0 so that no-profit scenarios (prices only going down) are handled automatically.

✅ The two pointer approach solves this in a single linear pass — O(N) time and O(1) space.

j always increments every iteration — i only moves when a cheaper price is found.


Happy Coding! If this clicked for you, the entire Stock series on LeetCode will feel much more approachable.

Ai Assistant Kas