123. Best Time to Buy and Sell Stock III
Problem Description
You are given an array prices
where prices[i]
represents the price of a stock on day i
.
Your goal is to find the maximum profit you can achieve by completing at most two transactions. A transaction consists of buying and then selling one share of the stock.
Important constraints:
- You can complete at most 2 transactions (buy-sell pairs)
- You cannot hold multiple stocks at the same time - you must sell your current stock before buying again
- You cannot sell a stock before buying it
The solution uses dynamic programming with four state variables:
f1
: Maximum profit after completing the first buy operationf2
: Maximum profit after completing the first sell operationf3
: Maximum profit after completing the second buy operationf4
: Maximum profit after completing the second sell operation
For each day's price, the code updates these states:
f1 = max(f1, -price)
: Either keep the previous buy state or buy at today's pricef2 = max(f2, f1 + price)
: Either keep the previous first-sell state or sell the first stock todayf3 = max(f3, f2 - price)
: Either keep the previous second-buy state or buy the second stock today using profit from first transactionf4 = max(f4, f3 + price)
: Either keep the previous second-sell state or sell the second stock today
The final answer is f4
, which represents the maximum profit after completing at most two transactions.
Intuition
The key insight is that with at most two transactions, we need to track the best profit at each possible stage of trading. Since we can't hold multiple stocks simultaneously, our trading follows a strict sequence: buy → sell → buy → sell.
At any point in time, we can be in one of four states:
- After first buy (holding first stock)
- After first sell (completed one transaction, holding cash)
- After second buy (holding second stock)
- After second sell (completed two transactions)
The challenge is determining when to make each move to maximize profit. For each day, we need to decide: should we maintain our current state or transition to the next state?
This naturally leads to a dynamic programming approach where we track the maximum profit achievable at each state. The transitions are:
- To buy first stock: We spend
price
, so profit becomes-price
- To sell first stock: We gain
price
added to our profit from buying - To buy second stock: We spend
price
from our first transaction's profit - To sell second stock: We gain
price
added to our second buy profit
The beauty of this approach is that we don't need to explicitly track which days we buy and sell. Instead, we just maintain the best possible profit at each state as we process each day. If buying or selling on a particular day doesn't improve our profit, we simply keep the previous state's value.
By considering all prices sequentially and updating our four states optimally, we ensure we capture the maximum profit possible with at most two transactions. The final answer is f4
because it represents the state after completing all possible transactions.
Solution Approach
The solution implements a state-based dynamic programming approach with four variables representing different trading states.
Initialization: We start by initializing the four state variables:
f1 = -prices[0]
: After buying the first stock on day 0, our profit is negative (we spent money)f2 = 0
: We haven't sold anything yet, so profit is 0f3 = -prices[0]
: If we were to buy a second stock on day 0 (hypothetically after a zero-profit first transaction)f4 = 0
: No second sale yet, profit is 0
State Transitions: For each subsequent day (starting from day 1), we update all four states:
-
First Buy (
f1
):f1 = max(f1, -price)
- Either keep the previous buy state (we already bought earlier at a better price) or buy today (starting fresh with
-price
profit)
-
First Sell (
f2
):f2 = max(f2, f1 + price)
- Either keep the previous sell state (we already sold at a better price) or sell today (adding today's price to our first buy profit)
-
Second Buy (
f3
):f3 = max(f3, f2 - price)
- Either keep the previous second buy state or buy today using profits from the first transaction (
f2 - price
)
-
Second Sell (
f4
):f4 = max(f4, f3 + price)
- Either keep the previous second sell state or sell today (adding today's price to our second buy profit)
Why This Works:
The algorithm ensures that at each step, we maintain the maximum possible profit for each state. The max
operations guarantee we only transition to a new state if it's profitable. If completing fewer than two transactions yields better profit, the algorithm naturally handles this - f4
will simply equal f2
(the profit from one transaction).
Time and Space Complexity:
- Time:
O(n)
where n is the length of the prices array (single pass) - Space:
O(1)
using only four variables regardless of input size
The final answer f4
represents the maximum profit achievable with at most two complete transactions.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the algorithm with prices = [3, 2, 6, 5, 0, 3]
.
Initial Setup (Day 0, price = 3):
f1 = -3
(bought first stock at price 3)f2 = 0
(haven't sold yet)f3 = -3
(hypothetical second buy)f4 = 0
(haven't completed second transaction)
Day 1 (price = 2):
f1 = max(-3, -2) = -2
(better to buy at price 2)f2 = max(0, -3 + 2) = 0
(selling would give -1 profit, so don't sell)f3 = max(-3, 0 - 2) = -2
(better to buy second stock now)f4 = max(0, -3 + 2) = 0
(selling would give -1, so don't sell)
Day 2 (price = 6):
f1 = max(-2, -6) = -2
(keep previous buy at price 2)f2 = max(0, -2 + 6) = 4
(sell first stock for profit of 4)f3 = max(-2, 0 - 6) = -2
(keep previous second buy)f4 = max(0, -2 + 6) = 4
(could sell second stock for profit of 4)
Day 3 (price = 5):
f1 = max(-2, -5) = -2
(keep buy at price 2)f2 = max(4, -2 + 5) = 4
(previous sell at 6 was better)f3 = max(-2, 4 - 5) = -1
(buy second stock using first transaction's profit)f4 = max(4, -2 + 5) = 4
(keep previous state)
Day 4 (price = 0):
f1 = max(-2, -0) = 0
(buying at 0 is better)f2 = max(4, -2 + 0) = 4
(keep previous sell)f3 = max(-1, 4 - 0) = 4
(buy second stock at price 0 using profit of 4)f4 = max(4, -1 + 0) = 4
(keep previous state)
Day 5 (price = 3):
f1 = max(0, -3) = 0
(keep buy at price 0)f2 = max(4, 0 + 3) = 4
(keep previous sell)f3 = max(4, 4 - 3) = 4
(keep second buy at price 0)f4 = max(4, 4 + 3) = 7
(sell second stock for total profit of 7)
Final Result: 7
The optimal strategy was:
- First transaction: Buy at price 2 (day 1), sell at price 6 (day 2) → profit = 4
- Second transaction: Buy at price 0 (day 4), sell at price 3 (day 5) → profit = 3
- Total profit = 4 + 3 = 7
Solution Implementation
1class Solution:
2 def maxProfit(self, prices: List[int]) -> int:
3 """
4 Calculate maximum profit from at most two stock transactions.
5 Uses dynamic programming with four states to track optimal profits.
6
7 Args:
8 prices: List of daily stock prices
9
10 Returns:
11 Maximum profit achievable with at most two transactions
12 """
13 # Initialize four states for tracking profits
14 # first_buy: Max profit after first stock purchase
15 # first_sell: Max profit after first stock sale
16 # second_buy: Max profit after second stock purchase
17 # second_sell: Max profit after second stock sale
18 first_buy = -prices[0]
19 first_sell = 0
20 second_buy = -prices[0]
21 second_sell = 0
22
23 # Iterate through remaining prices starting from day 2
24 for price in prices[1:]:
25 # Update states in reverse order to avoid using updated values
26 # Max profit after first buy: either keep previous state or buy at current price
27 first_buy = max(first_buy, -price)
28
29 # Max profit after first sell: either keep previous state or sell at current price
30 first_sell = max(first_sell, first_buy + price)
31
32 # Max profit after second buy: either keep previous state or buy again after first sell
33 second_buy = max(second_buy, first_sell - price)
34
35 # Max profit after second sell: either keep previous state or sell second stock
36 second_sell = max(second_sell, second_buy + price)
37
38 # Return maximum profit after at most two complete transactions
39 return second_sell
40
1class Solution {
2 public int maxProfit(int[] prices) {
3 // State variables for tracking profit at each transaction stage
4 // firstBuy: Maximum profit after first stock purchase
5 int firstBuy = -prices[0];
6
7 // firstSell: Maximum profit after first stock sale
8 int firstSell = 0;
9
10 // secondBuy: Maximum profit after second stock purchase
11 int secondBuy = -prices[0];
12
13 // secondSell: Maximum profit after second stock sale
14 int secondSell = 0;
15
16 // Iterate through each day's price starting from day 1
17 for (int i = 1; i < prices.length; i++) {
18 // Update maximum profit for first buy
19 // Either keep previous first buy or buy at today's price
20 firstBuy = Math.max(firstBuy, -prices[i]);
21
22 // Update maximum profit for first sell
23 // Either keep previous first sell or sell today after first buy
24 firstSell = Math.max(firstSell, firstBuy + prices[i]);
25
26 // Update maximum profit for second buy
27 // Either keep previous second buy or buy today after first sell
28 secondBuy = Math.max(secondBuy, firstSell - prices[i]);
29
30 // Update maximum profit for second sell
31 // Either keep previous second sell or sell today after second buy
32 secondSell = Math.max(secondSell, secondBuy + prices[i]);
33 }
34
35 // Return the maximum profit after at most two transactions
36 return secondSell;
37 }
38}
39
1class Solution {
2public:
3 int maxProfit(vector<int>& prices) {
4 // Initialize states for dynamic programming
5 // firstBuy: max profit after buying stock for the first time
6 int firstBuy = -prices[0];
7
8 // firstSell: max profit after selling stock for the first time
9 int firstSell = 0;
10
11 // secondBuy: max profit after buying stock for the second time
12 int secondBuy = -prices[0];
13
14 // secondSell: max profit after selling stock for the second time
15 int secondSell = 0;
16
17 // Iterate through all prices starting from day 1
18 for (int i = 1; i < prices.size(); ++i) {
19 // Update maximum profit for first buy
20 // Either keep previous first buy or buy at current price
21 firstBuy = max(firstBuy, -prices[i]);
22
23 // Update maximum profit for first sell
24 // Either keep previous first sell or sell at current price after first buy
25 firstSell = max(firstSell, firstBuy + prices[i]);
26
27 // Update maximum profit for second buy
28 // Either keep previous second buy or buy at current price after first sell
29 secondBuy = max(secondBuy, firstSell - prices[i]);
30
31 // Update maximum profit for second sell
32 // Either keep previous second sell or sell at current price after second buy
33 secondSell = max(secondSell, secondBuy + prices[i]);
34 }
35
36 // Return the maximum profit after at most 2 transactions
37 return secondSell;
38 }
39};
40
1/**
2 * Calculate maximum profit from at most two stock transactions
3 * @param prices - Array of stock prices where prices[i] is the price on day i
4 * @returns Maximum profit achievable with at most 2 transactions
5 */
6function maxProfit(prices: number[]): number {
7 // State variables for dynamic programming
8 // firstBuy: Maximum profit after first buy
9 // firstSell: Maximum profit after first sell
10 // secondBuy: Maximum profit after second buy
11 // secondSell: Maximum profit after second sell
12 let firstBuy: number = -prices[0];
13 let firstSell: number = 0;
14 let secondBuy: number = -prices[0];
15 let secondSell: number = 0;
16
17 // Iterate through each day's price starting from day 1
18 for (let i = 1; i < prices.length; i++) {
19 // Update states in reverse order to avoid using updated values
20 // For first transaction
21 firstBuy = Math.max(firstBuy, -prices[i]); // Either keep previous buy or buy today
22 firstSell = Math.max(firstSell, firstBuy + prices[i]); // Either keep previous sell or sell today
23
24 // For second transaction
25 secondBuy = Math.max(secondBuy, firstSell - prices[i]); // Either keep previous buy or buy today after first sell
26 secondSell = Math.max(secondSell, secondBuy + prices[i]); // Either keep previous sell or sell today
27 }
28
29 // Return maximum profit after at most two complete transactions
30 return secondSell;
31}
32
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the prices
array. The algorithm iterates through the prices array once using a single for loop that runs from index 1 to the end of the array. Each iteration performs a constant number of operations (four max
comparisons and arithmetic operations), resulting in linear time complexity.
Space Complexity: O(1)
. The algorithm uses only four variables (f1
, f2
, f3
, f4
) to track the states of buying and selling stocks, regardless of the input size. No additional data structures that scale with input size are used, making the space complexity constant.
Common Pitfalls
1. Incorrect State Update Order
A critical pitfall is updating the state variables in the wrong order within the loop, which causes the algorithm to use already-updated values from the current iteration instead of values from the previous iteration.
Problematic Code:
for price in prices[1:]:
first_buy = max(first_buy, -price)
first_sell = max(first_sell, first_buy + price) # Uses updated first_buy!
second_buy = max(second_buy, first_sell - price) # Uses updated first_sell!
second_sell = max(second_sell, second_buy + price) # Uses updated second_buy!
Why This Is Wrong:
When we update first_sell
, we're using the first_buy
value that was just updated in the same iteration. This means we could potentially be buying and selling on the same day, which violates the problem constraints.
Solution: Update states in reverse order or use temporary variables:
# Option 1: Reverse order updates
for price in prices[1:]:
second_sell = max(second_sell, second_buy + price)
second_buy = max(second_buy, first_sell - price)
first_sell = max(first_sell, first_buy + price)
first_buy = max(first_buy, -price)
# Option 2: Use temporary variables
for price in prices[1:]:
new_first_buy = max(first_buy, -price)
new_first_sell = max(first_sell, first_buy + price)
new_second_buy = max(second_buy, first_sell - price)
new_second_sell = max(second_sell, second_buy + price)
first_buy = new_first_buy
first_sell = new_first_sell
second_buy = new_second_buy
second_sell = new_second_sell
2. Misunderstanding the First Buy State
Another common mistake is initializing or updating first_buy
incorrectly by thinking it should account for previous profits.
Incorrect:
first_buy = max(first_buy, prev_profit - price) # Wrong!
Correct:
first_buy = max(first_buy, -price) # Correct - first buy starts from 0 profit
The first buy always starts from zero profit since it's the beginning of our trading sequence. Only the second buy (second_buy
) should consider profits from previous transactions.
3. Edge Case: Single Element Array
Failing to handle arrays with only one price can cause index out of bounds errors if not using the initialization approach shown.
Problematic Approach:
if len(prices) < 2:
return 0 # Need at least 2 days to make a transaction
Better Solution:
The provided solution naturally handles this by initializing all states with prices[0]
and then iterating from index 1, which works even for single-element arrays (the loop simply doesn't execute, returning 0).
What is the best way of checking if an element exists in a sorted array once in terms of time complexity? Select the best that applies.
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!