DSA
Best Time to Buy and Sell Stock - III
Covers: Recursive, Memoized. Optimal — Time O(n), Space O(2 * 3).
Practice Link
You are given an array prices where prices[i] is the price of a given stock on the ith day.
Find the maximum profit you can achieve. You may complete at most two transactions.
Note: You may not engage in multiple transactions simultaneously (i.e., you must sell the stock before you buy again).
Intiution#
At any point we have two options just keep check for the maximum possible transactions:
- Buy a stock (only if previously not bought)
- Do not sell - no transaction
- sell stock
- Sell a stock (only if previously bought)
- Do not buy - no transaction
- buy stock
Recursive Approach#
We extend the Stock II state space with a third dimension: the number of remaining transactions. The state (idx, canBuy, transactions) captures where we are, whether we can buy, and how many complete buy-sell rounds are still allowed. On every sell we decrement the transaction counter; when it hits zero or we exhaust all days, we return 0. The same state can be reached through many paths, causing exponential recomputation.
class Solution {
public:
int solve(vector<int> &prices, int idx, bool canBuy, int transactions)
{
if(transactions==0 || idx == prices.size())
return 0;
int profit = 0;
if(canBuy)
profit += max(solve(prices, idx+1, 1, transactions), -prices[idx] + solve(prices, idx+1, 0, transactions));
else
profit += max(solve(prices, idx+1, 0, transactions), prices[idx] + solve(prices, idx+1, 1, transactions-1));
return profit;
}
int maxProfit(vector<int>& prices) {
return solve(prices, 0,1, 2);
}
};
Time Complexity: O(2^n) --> TLE
Space Complexity: O(n)
Memoized Approach#
Memoization stores already computed results in a dp array to avoid recalculations. This optimizes the recursive approach, reducing redundant computations while still exploring all possible outcomes.
class Solution {
public:
int solve(vector<int> &prices, int idx, bool canBuy, int transactions, vector<vector<vector<int>>> &memo)
{
if(transactions==0 || idx == prices.size())
return 0;
int profit = 0;
if(memo[idx][canBuy][transactions] != -1)
return memo[idx][canBuy][transactions];
if(canBuy)
profit += max(solve(prices, idx+1, 1,transactions, memo), -prices[idx] + solve(prices, idx+1, 0,transactions, memo));
else
profit += max(solve(prices, idx+1, 0,transactions, memo), prices[idx] + solve(prices, idx+1, 1,transactions-1, memo));
return memo[idx][canBuy][transactions] = profit;
}
int maxProfit(vector<int>& prices) {
int transactions = 2;
vector<vector<vector<int>>> memo(prices.size(), vector<vector<int>> (2, vector<int>(transactions+1, -1)));
return solve(prices, 0, 1,transactions, memo);
}
};
Time Complexity: O(2 * 3 n) ~ O(n)
Space Complexity: O(2 * 3 * n) + O(n) (Recursive stack)
Tabulation#
Build the solution bottom-up with a 3D table dp[idx][buy][t] where t is the remaining transaction count. We fill the table from the last day back to day 0, ensuring that dp[idx+1] is always available when we compute dp[idx]. Each of the n × 2 × 3 cells is filled in O(1), giving O(n) overall time. The recursion stack overhead is eliminated compared to memoization.
class Solution {
public:
int maxProfit(vector<int>& prices) {
int transactions = 2;
vector<vector<vector<int>>> dp(prices.size()+1, vector<vector<int>> (2, vector<int>(transactions+1, 0)));
for(int idx=prices.size()-1;idx>=0;idx--)
{
for(int buy=0;buy<=1;buy++)
{
for(int t=1;t<=2;t++)
{
int profit=0;
if(buy)
profit = max(dp[idx+1][1][t], -prices[idx] + dp[idx+1][0][t]);
else
profit = max(dp[idx+1][0][t], prices[idx] + dp[idx+1][1][t-1]);
dp[idx][buy][t] = profit;
}
}
}
return dp[0][1][2];
}
};
Time Complexity: O(2 * 3 n) ~ O(n)
Space Complexity: O(2 * 3 * n)
Space Optimized Tabulation#
Since each row dp[idx] only reads from dp[idx+1], we can replace the full 3D table with two 2D arrays (next and curr) of size 2 × (transactions+1). After each day we swap curr into next. Space shrinks from O(2 × 3 × n) to O(2 × 3) — a constant — with identical time complexity.
class Solution {
public:
int maxProfit(vector<int>& prices) {
int transactions = 2;
vector<vector<int>> next(2, vector<int>(transactions+1, 0));
vector<vector<int>> curr(2, vector<int>(transactions+1, 0));
for(int idx=prices.size()-1;idx>=0;idx--)
{
for(int buy=0;buy<=1;buy++)
{
for(int t=1;t<=2;t++)
{
int profit=0;
if(buy)
profit = max(next[1][t], -prices[idx] + next[0][t]);
else
profit = max(next[0][t], prices[idx] + next[1][t-1]);
curr[buy][t] = profit;
}
}
next = curr;
}
return next[1][2];
}
};
Time Complexity: O(2 * 3 n) ~ O(n)
Space Complexity: O(2 * 3)