You are given an integer array prices where prices[i] is the price of a given stock on the ith day.
On each day, you may decide to buy and/or sell the stock. However, you can buy it then immediately sell it on the same day. Also, you are allowed to perform any number of transactions but can hold at most one share of the stock at any time.
Find and return the maximum profit you can achieve.
Example 1:
Input: prices =[7,1,5,3,6,4]Output:7
Explanation: Buy on day 2 (price = 1) and sell on day 3 (price = 5), profit = 5-1 = 4. Then buy on day 4 (price = 3) and sell on day 5 (price = 6), profit = 6-3 = 3. Total profit is 4 + 3 = 7.
Example 2:
Input: prices =[1,2,3,4,5]Output:4
Explanation: Buy on day 1 (price = 1) and sell on day 5 (price = 5), profit = 5-1 = 4. Total profit is 4.
Please upgrade to NeetCode Pro to view company tags.
Prerequisites
Before attempting this problem, you should be comfortable with:
Recursion - Foundation for exploring all buy/sell decision combinations
Dynamic Programming (Memoization) - Optimizes recursion by caching computed states
Dynamic Programming (Tabulation) - Builds solutions iteratively from base cases
Greedy Algorithms - Key insight that capturing every upward price movement is optimal
1. Recursion
Intuition
At each day, we have a choice: if we are not holding stock, we can either buy or skip. If we are holding stock, we can either sell or skip. We want to maximize profit by exploring all possible combinations of buy and sell decisions. This naturally leads to a recursive approach where we try both options at each step and return the maximum profit.
Algorithm
Define a recursive function rec(i, bought) where i is the current day and bought indicates if we are holding stock.
Base case: if we have processed all days, return 0.
At each day, we always have the option to skip (do nothing).
If we are holding stock (bought = true), we can sell at the current price and add it to our profit.
If we are not holding stock (bought = false), we can buy at the current price (subtract it from profit).
classSolution:defmaxProfit(self, prices: List[int])->int:defrec(i, bought):if i ==len(prices):return0
res = rec(i +1, bought)if bought:
res =max(res, prices[i]+ rec(i +1,False))else:
res =max(res,-prices[i]+ rec(i +1,True))return res
return rec(0,False)
publicclassSolution{publicintmaxProfit(int[] prices){returnrec(prices,0,false);}privateintrec(int[] prices,int i,boolean bought){if(i == prices.length){return0;}int res =rec(prices, i +1, bought);if(bought){
res =Math.max(res, prices[i]+rec(prices, i +1,false));}else{
res =Math.max(res,-prices[i]+rec(prices, i +1,true));}return res;}}
classSolution{public:intmaxProfit(vector<int>& prices){returnrec(prices,0,false);}private:intrec(vector<int>& prices,int i,bool bought){if(i == prices.size()){return0;}int res =rec(prices, i +1, bought);if(bought){
res =max(res, prices[i]+rec(prices, i +1,false));}else{
res =max(res,-prices[i]+rec(prices, i +1,true));}return res;}};
classSolution{/**
* @param {number[]} prices
* @return {number}
*/maxProfit(prices){constrec=(i, bought)=>{if(i === prices.length){return0;}let res =rec(i +1, bought);if(bought){
res = Math.max(res, prices[i]+rec(i +1,false));}else{
res = Math.max(res,-prices[i]+rec(i +1,true));}return res;};returnrec(0,false);}}
publicclassSolution{publicintMaxProfit(int[] prices){returnRec(prices,0,false);}privateintRec(int[] prices,int i,bool bought){if(i == prices.Length){return0;}int res =Rec(prices, i +1, bought);if(bought){
res = Math.Max(res, prices[i]+Rec(prices, i +1,false));}else{
res = Math.Max(res,-prices[i]+Rec(prices, i +1,true));}return res;}}
funcmaxProfit(prices []int)int{var rec func(i int, bought bool)int
rec =func(i int, bought bool)int{if i ==len(prices){return0}
res :=rec(i+1, bought)if bought {if prices[i]+rec(i+1,false)> res {
res = prices[i]+rec(i+1,false)}}else{if-prices[i]+rec(i+1,true)> res {
res =-prices[i]+rec(i+1,true)}}return res
}returnrec(0,false)}
class Solution {funmaxProfit(prices: IntArray): Int {funrec(i: Int, bought: Boolean): Int {if(i == prices.size)return0var res =rec(i +1, bought)if(bought){
res =maxOf(res, prices[i]+rec(i +1,false))}else{
res =maxOf(res,-prices[i]+rec(i +1,true))}return res
}returnrec(0,false)}}
classSolution{funcmaxProfit(_ prices:[Int])->Int{funcrec(_ i:Int,_ bought:Bool)->Int{if i == prices.count {return0}var res =rec(i +1, bought)if bought {
res =max(res, prices[i]+rec(i +1,false))}else{
res =max(res,-prices[i]+rec(i +1,true))}return res
}returnrec(0,false)}}
implSolution{pubfnmax_profit(prices:Vec<i32>)->i32{fnrec(prices:&[i32], i:usize, bought:bool)->i32{if i == prices.len(){return0;}letmut res =rec(prices, i +1, bought);if bought {
res = res.max(prices[i]+rec(prices, i +1,false));}else{
res = res.max(-prices[i]+rec(prices, i +1,true));}
res
}rec(&prices,0,false)}}
Time & Space Complexity
Time complexity: O(2n)
Space complexity: O(n)
2. Dynamic Programming (Top-Down)
Intuition
The recursive solution recalculates the same states multiple times. For example, the state (day 3, not holding) might be reached through different paths. By storing computed results in a memoization table, we avoid redundant work. Each unique state (day, holding_status) is computed only once.
Algorithm
Create a memoization table (dictionary or 2D array) to store results for each state.
Define a recursive function rec(i, bought) that first checks if the result is already cached.
If cached, return the stored result immediately.
Otherwise, compute the result by considering skip, buy, or sell options.
Store the result in the cache before returning.
Start the recursion from day 0 with no stock held.
classSolution:defmaxProfit(self, prices: List[int])->int:
dp ={}defrec(i, bought):if i ==len(prices):return0if(i, bought)in dp:return dp[(i, bought)]
res = rec(i +1, bought)if bought:
res =max(res, prices[i]+ rec(i +1,False))else:
res =max(res,-prices[i]+ rec(i +1,True))
dp[(i, bought)]= res
return res
return rec(0,False)
publicclassSolution{publicintmaxProfit(int[] prices){int n = prices.length;int[][] dp =newint[n][2];for(int i =0; i < n; i++){
dp[i][0]=-1;
dp[i][1]=-1;}returnrec(prices,0,0, dp);}privateintrec(int[] prices,int i,int bought,int[][] dp){if(i == prices.length){return0;}if(dp[i][bought]!=-1){return dp[i][bought];}int res =rec(prices, i +1, bought, dp);if(bought ==1){
res =Math.max(res, prices[i]+rec(prices, i +1,0, dp));}else{
res =Math.max(res,-prices[i]+rec(prices, i +1,1, dp));}
dp[i][bought]= res;return res;}}
classSolution{public:intmaxProfit(vector<int>& prices){int n = prices.size();
vector<vector<int>>dp(n,vector<int>(2,-1));returnrec(prices,0,0, dp);}private:intrec(vector<int>& prices,int i,int bought, vector<vector<int>>& dp){if(i == prices.size()){return0;}if(dp[i][bought]!=-1){return dp[i][bought];}int res =rec(prices, i +1, bought, dp);if(bought ==1){
res =max(res, prices[i]+rec(prices, i +1,0, dp));}else{
res =max(res,-prices[i]+rec(prices, i +1,1, dp));}
dp[i][bought]= res;return res;}};
classSolution{/**
* @param {number[]} prices
* @return {number}
*/maxProfit(prices){const n = prices.length;const dp = Array.from({length: n },()=>Array(2).fill(-1));constrec=(i, bought)=>{if(i === n){return0;}if(dp[i][bought]!==-1){return dp[i][bought];}let res =rec(i +1, bought);if(bought){
res = Math.max(res, prices[i]+rec(i +1,0));}else{
res = Math.max(res,-prices[i]+rec(i +1,1));}
dp[i][bought]= res;return res;};returnrec(0,0);}}
publicclassSolution{publicintMaxProfit(int[] prices){int n = prices.Length;int[,] dp =newint[n,2];for(int i =0; i < n; i++){
dp[i,0]=-1;
dp[i,1]=-1;}returnRec(prices,0,0, dp);}privateintRec(int[] prices,int i,int bought,int[,] dp){if(i == prices.Length){return0;}if(dp[i, bought]!=-1){return dp[i, bought];}int res =Rec(prices, i +1, bought, dp);if(bought ==1){
res = Math.Max(res, prices[i]+Rec(prices, i +1,0, dp));}else{
res = Math.Max(res,-prices[i]+Rec(prices, i +1,1, dp));}
dp[i, bought]= res;return res;}}
funcmaxProfit(prices []int)int{
n :=len(prices)
dp :=make([][2]int, n)for i :=range dp {
dp[i][0], dp[i][1]=-1,-1}var rec func(i, bought int)int
rec =func(i, bought int)int{if i == n {return0}if dp[i][bought]!=-1{return dp[i][bought]}
res :=rec(i+1, bought)if bought ==1{if prices[i]+rec(i+1,0)> res {
res = prices[i]+rec(i+1,0)}}else{if-prices[i]+rec(i+1,1)> res {
res =-prices[i]+rec(i+1,1)}}
dp[i][bought]= res
return res
}returnrec(0,0)}
class Solution {funmaxProfit(prices: IntArray): Int {val n = prices.size
val dp =Array(n){IntArray(2){-1}}funrec(i: Int, bought: Int): Int {if(i == n)return0if(dp[i][bought]!=-1)return dp[i][bought]var res =rec(i +1, bought)if(bought ==1){
res =maxOf(res, prices[i]+rec(i +1,0))}else{
res =maxOf(res,-prices[i]+rec(i +1,1))}
dp[i][bought]= res
return res
}returnrec(0,0)}}
classSolution{funcmaxProfit(_ prices:[Int])->Int{let n = prices.count
var dp =[[Int]](repeating:[Int](repeating:-1, count:2), count: n)funcrec(_ i:Int,_ bought:Int)->Int{if i == n {return0}if dp[i][bought]!=-1{return dp[i][bought]}var res =rec(i +1, bought)if bought ==1{
res =max(res, prices[i]+rec(i +1,0))}else{
res =max(res,-prices[i]+rec(i +1,1))}
dp[i][bought]= res
return res
}returnrec(0,0)}}
implSolution{pubfnmax_profit(prices:Vec<i32>)->i32{let n = prices.len();letmut dp =vec![[-1i32;2]; n];fnrec(prices:&[i32], i:usize, bought:usize, dp:&mutVec<[i32;2]>)->i32{if i == prices.len(){return0;}if dp[i][bought]!=-1{return dp[i][bought];}letmut res =rec(prices, i +1, bought, dp);if bought ==1{
res = res.max(prices[i]+rec(prices, i +1,0, dp));}else{
res = res.max(-prices[i]+rec(prices, i +1,1, dp));}
dp[i][bought]= res;
res
}rec(&prices,0,0,&mut dp)}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(n)
3. Dynamic Programming (Bottom-Up)
Intuition
Instead of solving recursively from day 0 forward, we can build the solution iteratively from the last day backward. At each day, we compute the maximum profit for both states (holding and not holding stock) using the already-computed values for the next day. This eliminates recursion overhead.
Algorithm
Create a 2D dp array where dp[i][0] is the max profit from day i when we can buy, and dp[i][1] is the max profit when we can sell.
Initialize the base case: dp[n][0] = dp[n][1] = 0 (no profit after the last day).
Iterate from the last day to the first day.
For each day, compute dp[i][0] as the max of skipping or buying (subtract price, transition to sell state).
Compute dp[i][1] as the max of skipping or selling (add price, transition to buy state).
Return dp[0][0] as we start without holding any stock.
publicclassSolution{publicintmaxProfit(int[] prices){int n = prices.length;int[][] dp =newint[n +1][2];for(int i = n -1; i >=0; i--){
dp[i][0]=Math.max(dp[i +1][0],-prices[i]+ dp[i +1][1]);
dp[i][1]=Math.max(dp[i +1][1], prices[i]+ dp[i +1][0]);}return dp[0][0];}}
classSolution{public:intmaxProfit(vector<int>& prices){int n = prices.size();
vector<vector<int>>dp(n +1,vector<int>(2,0));for(int i = n -1; i >=0; i--){
dp[i][0]=max(dp[i +1][0],-prices[i]+ dp[i +1][1]);
dp[i][1]=max(dp[i +1][1], prices[i]+ dp[i +1][0]);}return dp[0][0];}};
classSolution{/**
* @param {number[]} prices
* @return {number}
*/maxProfit(prices){const n = prices.length;const dp = Array.from({length: n +1},()=>Array(2).fill(0));for(let i = n -1; i >=0; i--){
dp[i][0]= Math.max(dp[i +1][0],-prices[i]+ dp[i +1][1]);
dp[i][1]= Math.max(dp[i +1][1], prices[i]+ dp[i +1][0]);}return dp[0][0];}}
publicclassSolution{publicintMaxProfit(int[] prices){int n = prices.Length;int[,] dp =newint[n +1,2];for(int i = n -1; i >=0; i--){
dp[i,0]= Math.Max(dp[i +1,0],-prices[i]+ dp[i +1,1]);
dp[i,1]= Math.Max(dp[i +1,1], prices[i]+ dp[i +1,0]);}return dp[0,0];}}
funcmaxProfit(prices []int)int{
n :=len(prices)
dp :=make([][2]int, n+1)for i := n -1; i >=0; i--{
dp[i][0]=max(dp[i+1][0],-prices[i]+dp[i+1][1])
dp[i][1]=max(dp[i+1][1], prices[i]+dp[i+1][0])}return dp[0][0]}funcmax(a, b int)int{if a > b {return a
}return b
}
class Solution {funmaxProfit(prices: IntArray): Int {val n = prices.size
val dp =Array(n +1){IntArray(2)}for(i in n -1 downTo 0){
dp[i][0]=maxOf(dp[i +1][0],-prices[i]+ dp[i +1][1])
dp[i][1]=maxOf(dp[i +1][1], prices[i]+ dp[i +1][0])}return dp[0][0]}}
classSolution{funcmaxProfit(_ prices:[Int])->Int{let n = prices.count
var dp =[[Int]](repeating:[0,0], count: n +1)for i instride(from: n -1, through:0, by:-1){
dp[i][0]=max(dp[i +1][0],-prices[i]+ dp[i +1][1])
dp[i][1]=max(dp[i +1][1], prices[i]+ dp[i +1][0])}return dp[0][0]}}
implSolution{pubfnmax_profit(prices:Vec<i32>)->i32{let n = prices.len();letmut dp =vec![[0i32;2]; n +1];for i in(0..n).rev(){
dp[i][0]= dp[i +1][0].max(-prices[i]+ dp[i +1][1]);
dp[i][1]= dp[i +1][1].max(prices[i]+ dp[i +1][0]);}
dp[0][0]}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(n)
4. Dynamic Programming (Space Optimized)
Intuition
Looking at the bottom-up solution, we notice that to compute the values for day i, we only need the values from day i+1. We do not need the entire dp array. This means we can reduce space from O(n) to O(1) by using just four variables to track the current and next day's states.
Algorithm
Initialize four variables: nextBuy, nextSell (for day i+1), and curBuy, curSell (for day i).
Start with all variables set to 0.
Iterate from the last day to the first day.
Compute curBuy as the max of skipping (nextBuy) or buying (-price + nextSell).
Compute curSell as the max of skipping (nextSell) or selling (price + nextBuy).
Update next variables with current values for the next iteration.
The key insight is that we can capture every upward price movement. If the price goes up from day i to day i+1, we can always "buy" on day i and "sell" on day i+1 to capture that profit. We do not need to track actual transactions because consecutive gains are equivalent to holding through multiple days. For example, buying at 1, holding through 3, 5, and selling at 6 gives the same profit as buying at 1, selling at 3, buying at 3, selling at 5, buying at 5, and selling at 6.
Algorithm
Initialize a profit variable to 0.
Iterate through the prices from day 1 to the last day.
If today's price is higher than yesterday's price, add the difference to profit.
This problem allows unlimited transactions, while Stock I only allows one. Using the Stock I approach (tracking min price and max difference) will undercount the total profit.
Missing Consecutive Gains
The greedy approach works because consecutive daily gains are equivalent to one larger transaction. Buying at 1, selling at 5 equals buying at 1, selling at 3, buying at 3, selling at 5. Missing this insight leads to overcomplicating the solution.
Adding Negative Profits
When using the greedy approach, only add the difference when prices[i] > prices[i-1]. Adding negative differences (price drops) reduces your profit incorrectly.
Sign in to join the discussion