DSA
Best Time to Buy and Sell Stock - IV
Covers: Recursive, Memoized. Optimal — Time O(kn), Space O(2 * k).
Practice Link
You are given an integer array prices where prices[i] is the price of a given stock on the ith day, and an integer k.
Find the maximum profit you can achieve. You may complete at most k transactions: i.e. you may buy at most k times and sell at most k times.
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#
This is the generalization of Stock III with at most k transactions instead of a fixed 2. The state (idx, canBuy, transactions) drives the recursion exactly as before: at each sell we decrement transactions, and we stop when no transactions remain or all days are exhausted. Since k can be large, the uncached recursion revisits the same states repeatedly and times out.
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(int k, vector<int>& prices) {
return solve(prices, 0,1, k);
}
};
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;
if(memo[idx][canBuy][transactions]!=-1)
return memo[idx][canBuy][transactions];
int profit = 0;
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(int k, vector<int>& prices) {
vector<vector<vector<int>>> memo(prices.size(), vector<vector<int>>(2, vector<int>(k+1, -1)));
return solve(prices, 0, 1, k, memo);
}
};
Time Complexity: O(2 * k * n) ~ O(kn)
Space Complexity: O(2 * k * n) + O(kn) (Recursive stack)
Tabulation#
Build a 3D table dp[idx][canBuy][t] iterating from the last day back to day 0, with each cell computed in O(1) from the next day's row. The total states are n × 2 × (k+1), so overall time is O(kn) and space is O(kn). By going bottom-up we remove the call-stack overhead of memoization.
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
vector<vector<vector<int>>> dp(prices.size()+1, vector<vector<int>>(2, vector<int>(k+1, 0)));
for(int idx = prices.size()-1;idx>=0;idx--)
{
for(int canBuy=0;canBuy<=1;canBuy++)
{
for(int t = 1;t<=k;t++)
{
int profit=0;
if(canBuy)
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][canBuy][t] = profit;
}
}
}
return dp[0][1][k];
}
};
Time Complexity: O(2 * k * n) ~ O(kn)
Space Complexity: O(2 * k * n)
Space Optimized Tabulation#
Each day's row depends only on the next day's row, so we keep two 2D arrays — next (size 2 × (k+1)) and curr — and swap them after each day. Space collapses from O(2kn) to O(2k) while time stays O(kn).
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
vector<vector<int>> next(2, vector<int>(k+1, 0));
vector<vector<int>> curr(2, vector<int>(k+1, 0));
for(int idx = prices.size()-1;idx>=0;idx--)
{
for(int canBuy=0;canBuy<=1;canBuy++)
{
for(int t = 1;t<=k;t++)
{
int profit=0;
if(canBuy)
profit = max(next[1][t], -prices[idx]+next[0][t]);
else
profit = max(next[0][t], prices[idx]+next[1][t-1]);
curr[canBuy][t] = profit;
next = curr;
}
}
}
return next[1][k];
}
};
Time Complexity: O(2 * k * n) ~ O(kn)
Space Complexity: O(2 * k)