Editorial
Core insight
Best Time to Buy and Sell Stock III · Dynamic Programming (DP) Patterns
Core Insight for Best Time to Buy and Sell Stock III
The key intuition is to view the problem as a state machine. Instead of trying to find the specific days to buy and sell, we focus on the states our portfolio can be in at the end of any given day.
Since we are allowed at most two transactions, there are exactly four relevant states of "being" in the market (excluding the initial state of 0 profit):
- State 1 (Buy 1): We have bought the first stock and are currently holding it.
- State 2 (Sell 1): We have sold the first stock and realized a profit.
- State 3 (Buy 2): We have bought the second stock (using the proceeds from the first sale) and are holding it.
- State 4 (Sell 2): We have sold the second stock and realized the final profit.
Visual Description: Imagine a flow diagram where data moves from left to right.
- The
prices[i]input flows into Buy 1, updating the "cost basis" (minimizing cost). - The output of Buy 1 flows into Sell 1, updating the realized profit.
- The output of Sell 1 flows into Buy 2, effectively reducing the cost of the second stock by the profit gained from the first.
- The output of Buy 2 flows into Sell 2, which represents the final cumulative profit.
The invariant here is that Sell 2 will always contain the maximum possible profit after at most two transactions because it builds upon the optimal substructures of the previous states.
Deep dive
Full breakdown
Full reasoning, pitfalls, and implementation flow
LeetCode 123: Best Time to Buy and Sell Stock III Solution & Explanation
Problem Overview
TL;DR: We solve this by modeling the problem as a finite state machine with four variables tracking the maximum profit for the first buy, first sell, second buy, and second sell respectively.
The Best Time to Buy and Sell Stock III problem asks us to find the maximum profit obtainable from a series of stock prices given a specific constraint: we can complete at most two transactions. A transaction consists of buying and then selling. Importantly, we cannot engage in multiple transactions simultaneously; we must sell our current holding before buying again.
This is a popular interview question because it evolves the simple "buy low, sell high" logic into a multi-stage decision process. The LeetCode 123 solution requires handling dependencies between the first and second transactions efficiently.
Brute Force Approach for Best Time to Buy and Sell Stock III
A naive approach attempts to divide the problem into two independent single-transaction problems. Since we can make at most two transactions, there must be a dividing point in the array where the first transaction happens on or before day , and the second transaction happens after day .
The algorithm would look like this:
- Iterate through every possible split point from to .
- For each , calculate the maximum profit for one transaction in the subarray
prices[0...i]. - Calculate the maximum profit for one transaction in the subarray
prices[i...n-1]. - Sum these two profits.
- Track the maximum sum found across all split points.
Pseudo-code:
max_total_profit = 0
for i from 0 to length(prices):
left_profit = max_profit_1_trans(prices[0...i])
right_profit = max_profit_1_trans(prices[i...length])
max_total_profit = max(max_total_profit, left_profit + right_profit)
return max_total_profitComplexity Analysis: The time complexity is . Calculating the maximum profit for a subarray takes time. Since we repeat this calculation for every possible split point (where there are split points), the total operations are proportional to .
Why it fails:
With constraints where prices.length can be up to , an solution requires roughly operations. This far exceeds the typical limit of operations per second, resulting in a Time Limit Exceeded (TLE) error.
Algorithm Strategy: Dynamic Programming (DP) Patterns
We utilize the Dynamic Programming pattern by maintaining four variables representing the maximum profit (or best balance) achievable for each of the four states described above.
Variables to Maintain:
t1_cost: The lowest price paid for the first stock so far. (Alternatively, the max balance after the first buy, which is negative price).t1_profit: The maximum profit after the first sell.t2_cost: The lowest effective price paid for the second stock. We calculate this asprice - t1_profit. This effectively reinvests our first profit to lower the cost of the second buy.t2_profit: The maximum total profit after the second sell.
Constraints Enforced:
- We update these variables sequentially for each price in the array.
- The order of updates matters to simulate the timeline correctly (though since we can buy and sell on the same day, updating in a single pass works).
t1_profitdepends ont1_cost.t2_costdepends ont1_profit.t2_profitdepends ont2_cost.
By the end of the iteration, t2_profit will hold the maximum profit for two transactions. If only one transaction was optimal, the math naturally carries the single transaction profit through to t2_profit.
Execution Flow
-
Initialize Variables:
t1_cost: Infinity (orprices[0]) representing the min cost to buy stock 1.t1_profit: 0.t2_cost: Infinity (orprices[0]) representing the min cost to buy stock 2 (factoring in previous profit).t2_profit: 0.
-
Iterate through each
priceinprices:- Step 2a: Update
t1_cost. It is the minimum of the currentt1_costand the currentprice. - Step 2b: Update
t1_profit. It is the maximum of the currentt1_profitandprice - t1_cost. - Step 2c: Update
t2_cost. It is the minimum of the currentt2_costandprice - t1_profit. (Think of this as: "What is the price of the second stock, heavily discounted by the money we made in the first trade?"). - Step 2d: Update
t2_profit. It is the maximum of the currentt2_profitandprice - t2_cost.
- Step 2a: Update
-
Return
t2_profit.
Proof of Correctness
The correctness relies on the optimal substructure property of Dynamic Programming.
t1_profitat step correctly stores the max profit for 1 transaction using prices up to day .t2_costusest1_profit. By minimizingprice - t1_profit, we are mathematically equivalent to maximizingt1_profit - price. This represents the best state of our wallet after buying the second stock.t2_profitmaximizesprice - t2_cost. Substitutingt2_cost, we are maximizingprice_sell2 - (price_buy2 - profit_trans1), which simplifies toprofit_trans1 + (price_sell2 - price_buy2).- Therefore,
t2_profitalways accumulates the maximum sum of profits from two non-overlapping transactions.
Pattern Reuse Notes
The DP - Stock problems pattern used here is directly applicable to several other LeetCode problems. Mastering the state machine approach allows you to solve variations by simply adjusting the state transitions.
- LeetCode 121: Best Time to Buy and Sell Stock: This is the base case (). It is equivalent to calculating just
t1_profit. - LeetCode 122: Best Time to Buy and Sell Stock II: This allows infinite transactions (). The greedy approach works here, but it can also be modeled as a state machine where the "sell" state loops back to the "buy" state.
- LeetCode 188: Best Time to Buy and Sell Stock IV: This is the generalized version of Problem 123 where is an input. You simply use arrays of size instead of 4 individual variables.
- LeetCode 309: Best Time to Buy and Sell Stock with Cooldown: This introduces a "Cooldown" state. The pattern is identical, but the state diagram adds a mandatory rest node between selling and buying again.
Feynman coach · Before you peek at the code
Can you explain the core insight in your own words?
If you can explain it before reading code, you’re far more likely to recall it under interview pressure.
Reference Implementation
C++ Solution for LeetCode 123
1#include <vector>
2#include <algorithm>
3#include <climits>
4
5class Solution {
6public:
7 int maxProfit(std::vector<int>& prices) {
8 // Initialize costs to max integer to ensure the first price is picked up
9 int t1_cost = INT_MAX;
10 int t2_cost = INT_MAX;
11
12 // Profits start at 0
13 int t1_profit = 0;
14 int t2_profit = 0;
15
16 for (int price : prices) {
17 // Minimize cost for first buy
18 t1_cost = std::min(t1_cost, price);
19
20 // Maximize profit for first sell
21 t1_profit = std::max(t1_profit, price - t1_cost);
22
23 // Minimize effective cost for second buy (price - previous profit)
24 t2_cost = std::min(t2_cost, price - t1_profit);
25
26 // Maximize profit for second sell
27 t2_profit = std::max(t2_profit, price - t2_cost);
28 }
29
30 return t2_profit;
31 }
32};Java Solution for LeetCode 123
1class Solution {
2 public int maxProfit(int[] prices) {
3 // Initialize costs to MAX_VALUE to serve as the baseline for minimization
4 int t1Cost = Integer.MAX_VALUE;
5 int t2Cost = Integer.MAX_VALUE;
6
7 // Profits are initially 0
8 int t1Profit = 0;
9 int t2Profit = 0;
10
11 for (int price : prices) {
12 // 1. Buy 1: Minimize the cost of the first stock
13 t1Cost = Math.min(t1Cost, price);
14
15 // 2. Sell 1: Maximize profit from the first transaction
16 t1Profit = Math.max(t1Profit, price - t1Cost);
17
18 // 3. Buy 2: Minimize the effective cost of the second stock
19 // Effect: We reinvest t1Profit into this purchase
20 t2Cost = Math.min(t2Cost, price - t1Profit);
21
22 // 4. Sell 2: Maximize total profit from two transactions
23 t2Profit = Math.max(t2Profit, price - t2Cost);
24 }
25
26 return t2Profit;
27 }
28}Python Solution for LeetCode 123
1class Solution:
2 def maxProfit(self, prices: list[int]) -> int:
3 # Initialize costs to infinity
4 t1_cost = float('inf')
5 t2_cost = float('inf')
6
7 # Initialize profits to 0
8 t1_profit = 0
9 t2_profit = 0
10
11 for price in prices:
12 # First transaction: Buy
13 t1_cost = min(t1_cost, price)
14
15 # First transaction: Sell
16 t1_profit = max(t1_profit, price - t1_cost)
17
18 # Second transaction: Buy (reinvesting profit)
19 t2_cost = min(t2_cost, price - t1_profit)
20
21 # Second transaction: Sell
22 t2_profit = max(t2_profit, price - t2_cost)
23
24 return t2_profit