Problem Statement
Given an array arr
of n
integers, where arr[i]
represents price of the stock on the ith
day. Determine the maximum profit achievable by buying and selling the stock any number of times.
Holding at most one share of the stock at any given time is allowed, meaning buying and selling the stock can be done any number of times, but the stock must be sold before buying it again. Buying and selling the stock on the same day is permitted.
Examples
Example 1:
Input: arr = [9, 2, 6, 4, 7, 3]
Output: 7
Explanation: Buy on day 2 (price = 2) and sell on day 3 (price = 6), profit = 6 - 2 = 4. Then buy on day 4 (price = 4) and sell on day (price = 7), profit = 7 - 4 = 3. Total profit = 4 + 3 = 7.
Example 2:
Input: arr = [2, 3, 4, 5, 6]
Output: 4
Explanation: Buy on day 1 (price = 2) and sell on day 5 (price = 6), profit = 6 - 2 = 4. Total profit is 4.
Different Approaches
1️⃣ Recursive Approach
Upon observation we find that every day, there will be two choices, either to do nothing and move to the next day or to buy/sell (based on the last transaction) and find out the profit. Therefore we need to generate all the choices in order to compare the profit. As we need to try out all the possible choices, we will use recursion.
Steps to form the recursive solution:
- Express the problem in terms of indexes: We need to consider the number of days, so one variable will be the array index (let's call it 'ind'). Additionally, we need to adhere to the condition that a stock cannot be bought again until it has been sold first. Therefore, a second variable 'buy' is needed to indicate whether the stock can be bought or sold on a particular day.
f(ind, buy) will give the maximum profit that can be generated from day 'ind' to day (n-1), where 'buy' tells that whether we need to buy or sell the stock at day 'ind'. - Try out all possible choices at a given index: Every day, there will be two choices, either to buy/sell the stock(based on the buy variable’s value) or to do nothing and move on to the next day. As we need to generate all the choices, use the pick/non-pick technique to the same.
- Case 1: When buy == 0, the stock can be bought. If we can buy the stock on a particular day, there will be again two options:
- Option 1: To do no transaction and move to the next day. In this case, the net profit earned will be 0 from the current transaction, and to calculate the maximum profit starting from the next day, we will recursively call f(ind+1,0). As we have not bought the stock, the ‘buy’ variable value will still remain 0, indicating that we can buy the stock the next day.
- Option 2: The other option is to buy the stock on the current day. In this case, the net profit earned from the current transaction will be -arr[i]. As we are buying the stock, it means we are giving money out of our pocket, therefore the profit earned is negative. To calculate the maximum profit starting from the next day, we will recursively call f(ind+1,1). As we have bought the stock, the ‘buy’ variable value will change to 1, indicating that we can’t buy and only sell the stock the next day.
- Case 2: When
buy == 1
, means we are holding a stock, we can sell the stock. If we can sell the stock on a particular day, we will have two options:- Option 1: To do no transaction and move to the next day. In this case, the net profit earned will be 0 from the current transaction, and to calculate the maximum profit starting from the next day, we will recursively call f(ind+1,1). As we have not bought the stock, the ‘buy’ variable value will still remain at 1, indicating that we can’t buy and only sell the stock the next day.
- Option 2: The other option is to sell the stock on the current day. In this case, the net profit earned from the current transaction will be +arr[i]. As we are selling the stock, we are putting the money into our pocket, therefore the profit earned is positive. To calculate the maximum profit starting from the next day, we will recursively call f(ind+1,0). As the stock has been sold, the ‘buy’ variable value will change to 0, indicating that we can buy the stock the next day.
![image-299.png](https://thejat.in/storage/image-299.png)
/*It is pseudocode and it is not tied to
any specific programming language*/
f(ind, buy){
// Base case here
if(buy == 0){
// buy
// do nothing and proceed to next
// index for buying.
op1 = 0 + f(ind + 1, 0)
// buy the current and proceed to next
// index for selling
op2 = -arr[ind] + f(ind + 1, 1)
}
if(buy == 1){
// sell
// do nothing and proceed to next
// index for selling.
op1 = 0 + f(ind + 1, 1)
// sell the current one, make profit and
// proceed to next index to buy again.
op2 = arr[ind] + f(ind + 1, 0)
}
}
- Take the maximum of all choices: As we are looking to maximize the profit earned, return the maximum value in both cases.
/*It is pseudocode and it is not tied to
any specific programming language*/
f(ind, buy){
//Base case here
if(buy == 0){
// buy
// do nothing and proceed to next
// index for buying.
op1 = 0 + f(ind + 1, 0)
// buy the current and proceed to next
// index for selling
op2 = -arr[ind] + f(ind + 1, 1)
}
if(buy == 1){
// sell
// do nothing and proceed to next
// index for selling.
op1 = 0 + f(ind + 1, 1)
// sell the current one, make profit and
// proceed to next index to buy again.
op2 = arr[ind] + f(ind + 1, 0)
}
// max of do nothing and buy/sell
return max(op1, op2)
}
- Base cases:
- If
ind == n
, it means we have finished trading on all days, and there is no more money that we can get, therefore simply return0
. No more transactions to made.
- If
Recursive Tree:
prices = [3, 2, 6, 5, 0, 3]
We start from day 0
with no stock (0, 0
), and each step, evaluate all possible decisions recursively.
Legend:
(i, holding)
:i
: Current day.holding
:1
if holding stock,0
otherwise.
- Decisions:
- Buy: Subtract the current price from profit.
- Sell: Add the current price to profit.
- Skip or Hold: Move to the next day without a transaction.
(0, 0) [Day 0, not holding]
├── Buy: (1, 1) --> Profit = -3
│ ├── Sell: (2, 0) --> Profit = -3 + 2 = -1
│ │ ├── Buy: (3, 1) --> Profit = -1 - 6 = -7
│ │ │ ├── Sell: (4, 0) --> Profit = -7 + 5 = -2
│ │ │ │ ├── Buy: (5, 1) --> Profit = -2 - 0 = -2
│ │ │ │ │ ├── Sell: (6, 0) --> Profit = -2 + 3 = 1
│ │ │ │ │ └── Skip: (6, 1) --> Profit = -2
│ │ │ │ └── Skip: (5, 0) --> Profit = -2
│ │ │ └── Hold: (4, 1) --> Profit = -7
│ │ └── Skip: (3, 0) --> Profit = -1
│ └── Hold: (2, 1) --> Profit = -3
│ ├── Sell: (3, 0) --> Profit = -3 + 6 = 3
│ │ ├── Buy: (4, 1) --> Profit = 3 - 5 = -2
│ │ │ ├── Sell: (5, 0) --> Profit = -2 + 0 = -2
│ │ │ └── Skip: (5, 1) --> Profit = -2
│ │ └── Skip: (4, 0) --> Profit = 3
│ └── Hold: (3, 1) --> Profit = -3
└── Skip: (1, 0) [Day 1, not holding]
├── Buy: (2, 1) --> Profit = -2
│ ├── Sell: (3, 0) --> Profit = -2 + 6 = 4
│ │ ├── Buy: (4, 1) --> Profit = 4 - 5 = -1
│ │ │ ├── Sell: (5, 0) --> Profit = -1 + 0 = -1
│ │ │ └── Skip: (5, 1) --> Profit = -1
│ │ └── Skip: (4, 0) --> Profit = 4
│ └── Hold: (3, 1) --> Profit = -2
└── Skip: (2, 0) --> Profit = 0
Code:
class Solution {
public:
/*
Recursive function to calculate the maximum profit.
@param prices - A vector containing the prices of the stock on different days.
@param currentIndex - The current day index being processed.
@param canBuy - A flag indicating whether buying is allowed (1 for buy, 0 for sell).
@return Maximum profit that can be achieved from the current state.
*/
int recursive(vector<int>& prices, int currentIndex, int canBuy) {
// Base Case: If we reach the last day, no further transactions can be made
if (currentIndex >= prices.size()) {
return 0;
}
int doNothingProfit = 0; // Profit when no action is taken
int transactionProfit = 0; // Profit when a transaction (buy/sell) is performed
if (canBuy == 1) {
// Buying is allowed
// Option 1: Do nothing, move to the next day
doNothingProfit = recursive(prices, currentIndex + 1, 1);
// Option 2: Buy the stock, reduce the profit by the stock price, and switch to selling state
transactionProfit = -prices[currentIndex] + recursive(prices, currentIndex + 1, 0);
} else {
// Selling is allowed
// Option 1: Do nothing, move to the next day
doNothingProfit = recursive(prices, currentIndex + 1, 0);
// Option 2: Sell the stock, add the stock price to profit, and switch to buying state
transactionProfit = prices[currentIndex] + recursive(prices, currentIndex + 1, 1);
}
// Return the maximum profit between doing nothing and performing a transaction
return max(doNothingProfit, transactionProfit);
}
/*
Function to calculate the maximum profit using recursive approach.
@param prices - A vector containing the prices of the stock on different days.
@param n - The total number of days (size of the prices array).
@return Maximum profit that can be achieved.
*/
int stockBuySell(vector<int> prices, int n) {
return recursive(prices, 0, 1); // Start from day 0 with buying allowed
}
};
OR:
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
int maxProfitRecursive(int index, bool holding, const vector<int>& prices) {
// Base case: If we reach the end of the array
if (index == prices.size()) {
return 0;
}
// If not holding a stock
if (!holding) {
int buy = -prices[index] + maxProfitRecursive(index + 1, true, prices);
int skip = maxProfitRecursive(index + 1, false, prices);
return max(buy, skip);
} else { // If holding a stock
int sell = prices[index] + maxProfitRecursive(index + 1, false, prices);
int hold = maxProfitRecursive(index + 1, true, prices);
return max(sell, hold);
}
}
int maxProfit(vector<int>& prices) {
return maxProfitRecursive(0, false, prices);
}
int main() {
vector<int> prices = {7, 1, 5, 3, 6, 4};
cout << "Maximum Profit: " << maxProfit(prices) << endl;
return 0;
}
Complexity Analysis:
- Time Complexity:
O(2^(N))
, whereN
is the number of row. As, for each index, there are two possible options.- This is because each day has two choices (
buy/sell or skip/hold
), leading to an exponential number of recursive calls.
- This is because each day has two choices (
- Space Complexity:
O(N)
, at maximum the depth of the recursive stack can go up-to N.
2️⃣ Memoization
If we draw the recursion tree, we will see that there are overlapping subproblems. Hence the DP approaches can be applied to the recursive solution.
![image-301.png](https://thejat.in/storage/image-301.png)
In order to convert a recursive solution to memoization the following steps will be taken:
- Declare a dp array of size [n][2]: As there are two changing parameters in the recursive solution, 'ind' and 'buy'. The maximum value 'ind' can attain is (n), where n is the size of array and for 'buy' only two values possible 0, 1.
- The dp array stores the calculations of subproblems. Initialize dp array with -1, to indicate that no subproblem has been solved yet.
- While encountering an overlapping subproblem: When we come across a subproblem, for which the dp array has value other than -1, it means we have found a subproblem which has been solved before hence it is an overlapping subproblem. No need to calculate it's value again just retrieve the value from dp array and return it.
- Store the value of subproblem: Whenever, a subproblem is encountered and it is not solved yet(the value at this index will be -1 in the dp array), store the calculated value of subproblem in the array.
Code:
#include <vector>
#include <iostream>
#include <cstring> // For memset
using namespace std;
// Helper function with memoization
int maxProfitMemo(int index, bool holding, const vector<int>& prices, vector<vector<int>>& dp) {
// Base case: If we've reached the end of the prices array
if (index == prices.size()) {
return 0;
}
// If this state has been computed, return it
if (dp[index][holding] != -1) {
return dp[index][holding];
}
// If not holding a stock
if (!holding) {
int buy = -prices[index] + maxProfitMemo(index + 1, true, prices, dp);
int skip = maxProfitMemo(index + 1, false, prices, dp);
dp[index][holding] = max(buy, skip);
} else { // If holding a stock
int sell = prices[index] + maxProfitMemo(index + 1, false, prices, dp);
int hold = maxProfitMemo(index + 1, true, prices, dp);
dp[index][holding] = max(sell, hold);
}
return dp[index][holding];
}
// Main function
int maxProfit(vector<int>& prices) {
int n = prices.size();
vector<vector<int>> dp(n, vector<int>(2, -1)); // Initialize dp table with -1
return maxProfitMemo(0, false, prices, dp); // Start from day 0 with no stock
}
int main() {
vector<int> prices = {3, 2, 6, 5, 0, 3};
cout << "Maximum Profit: " << maxProfit(prices) << endl;
return 0;
}
Complexity Analysis:
- Time Complexity:
O(N*2)
, There areN*2
states, therefore at max ‘N*2’ new problems will be solved. - Space Complexity:
O(N*2) + O(N)
, As we are using a recursion stack space of O(N) and a 2D array O(N*2).
3️⃣ Tabulation
In order to convert a recursive code to tabulation code, we try to convert the recursive code to tabulation and here are the steps:
- Declare a dp array of size [n+1][2]: As there are two changing parameters in the recursive solution, 'ind' and 'buy'. The maximum value 'ind' can attain is (n) and including 0 its n+1, where n is the size of array and for 'buy' only two values possible 0, 1.
- Setting Base Cases in the Array: In the recursive code, our base condition was when If ind == n, it means we have finished trading on all days, and there is no more money that we can get, therefore simply return 0. As this is the last row, so set dp[n][0] = dp[n][1] = 0(the case when we had exhausted the number of days of the stock market).
![image-342.png](https://thejat.in/storage/image-342.png)
- Iterative Computation Using Loops: Set two nested for loops, the outer loop (for variable ind) moves from n-1 to 0 and the inner loop (for variable buy) moves from 0 to 1. In every iteration, calculate the value according to the memoization logic.
![image-341.png](https://thejat.in/storage/image-341.png)
- Choosing the maximum: As we are looking to maximize the profit earned, return the maximum value in all the cases as discussed in memoization approach.
- Returning the answer: The element dp[0][0] of the dp array is returned because it holds the optimal solution to the entire problem after the computation has been completed.
Intuition:
- We create a table (
dp
) to iteratively compute the maximum profit for each day (index
) and each state (holding
or not holding`). - The profit at any state
(index, holding)
depends only on the decisions we can take at that state:- If not holding a stock:
- Buy the stock, leading to the next state
(index + 1, holding = 1)
. - Skip the transaction, staying in
(index + 1, holding = 0)
.
- Buy the stock, leading to the next state
- If holding a stock:
- Sell the stock, leading to the next state
(index + 1, holding = 0)
. - Hold the stock, staying in
(index + 1, holding = 1)
.
- Sell the stock, leading to the next state
- If not holding a stock:
- Use these transitions to compute the solution iteratively for all states.
Approach:
- Define State Variables:
dp[i][0]
: Maximum profit on dayi
if not holding a stock.dp[i][1]
: Maximum profit on dayi
if holding a stock.
- Base Case:
- On the last day (
n - 1
):dp[n - 1][0] = 0
: If not holding a stock, no profit can be made.dp[n - 1][1] = -prices[n - 1]
: If holding a stock, the maximum profit is the negative value of the stock price.
- On the last day (
- Transition:
- If not holding a stock (
dp[i][0]
):dp[i][0] = max(dp[i + 1][0], dp[i + 1][1] + prices[i])
- Skip:
dp[i + 1][0]
- Sell:
dp[i + 1][1] + prices[i]
- Skip:
- If holding a stock (
dp[i][1]
):dp[i][1] = max(dp[i + 1][1], dp[i + 1][0] - prices[i])
- Hold:
dp[i + 1][1]
- Buy:
dp[i + 1][0] - prices[i]
- Hold:
- If not holding a stock (
- Iterate Backwards:
- Start from the second-to-last day (
n - 2
) and computedp[i][0]
anddp[i][1]
for all days.
- Start from the second-to-last day (
- Final Result:
- The answer is
dp[0][0]
, as we begin on day 0 without holding any stock.
- The answer is
Code:
#include <bits/stdc++.h>
using namespace std;
class Solution{
private:
//Function to find maximum profit earned using tabulation
int func(vector<int> &arr, int n) {
// Create a DP table to memoize results
vector<vector<int>> dp(n + 1, vector<int>(2, -1));
// Base condition
dp[n][0] = dp[n][1] = 0;
int profit;
// Loop through the array in reverse order
for (int ind = n - 1; ind >= 0; ind--) {
for (int buy = 0; buy <= 1; buy++) {
// We can buy the stock
if (buy == 0) {
profit = max(0 + dp[ind + 1][0], (-1)*arr[ind] + dp[ind + 1][1]);
}
// We can sell the stock
if (buy == 1) {
profit = max(0 + dp[ind + 1][1], arr[ind] + dp[ind + 1][0]);
}
dp[ind][buy] = profit;
}
}
/* The maximum profit is stored in
dp[0][0] after all calculations*/
return dp[0][0];
}
public:
//Function to calculate the maximum profit earned
int stockBuySell(vector<int> &arr, int n){
//Return the maximum profit
return func(arr, n);
}
};
int main() {
int n = 6;
vector<int> arr = {7, 1, 5, 3, 6, 4};
//Create an instance of Solution class
Solution sol;
// Call the getMaximumProfit function and print the result
cout << "The maximum profit that can be generated is " << sol.stockBuySell(arr, n);
return 0;
}
OR:
#include <vector>
#include <iostream>
using namespace std;
int maxProfit(vector<int>& prices) {
int n = prices.size();
if (n == 0) return 0;
// dp[i][0] = max profit on day i if not holding a stock
// dp[i][1] = max profit on day i if holding a stock
vector<vector<int>> dp(n, vector<int>(2, 0));
// Base cases for the last day
dp[n - 1][0] = 0; // Not holding a stock on the last day
dp[n - 1][1] = -prices[n - 1]; // Holding a stock on the last day
// Fill the dp table from the second-to-last day to the first day
for (int i = n - 2; i >= 0; i--) {
dp[i][0] = max(dp[i + 1][0], dp[i + 1][1] + prices[i]); // Max of skipping or selling
dp[i][1] = max(dp[i + 1][1], dp[i + 1][0] - prices[i]); // Max of holding or buying
}
// The result is the maximum profit starting on day 0 without holding stock
return dp[0][0];
}
int main() {
vector<int> prices = {3, 2, 6, 5, 0, 3};
cout << "Maximum Profit: " << maxProfit(prices) << endl;
return 0;
}
Complexity Analysis:
- Time Complexity:
O(N*2)
, As, here are two nested loops that account for O(N*2) complexity. - Space Complexity:
O(N*2)
, As a 2D array of size N*2 is used
4️⃣ Space Optimized