Jb Tak Fodega Nhi 🎯 Tb Tk Chodega
Nhi 📚 (MAANG)
DPL37 Best Time to Buy and Sell Stock III
We are given an array Arr[] of length n. It represents the price of a stock on ‘n’
days. The following guidelines need to be followed:
Find and return the maximum profit you can achieve.
Only Few Updates in this Question As Compared to the Last, DPL36 Best Time to Buy and Sell Stock II
In Best Time to Buy and Sell Stock III We Allow Just Only 2 Transactions Thsi
Probelms Give a Reminder of the Knapsack Probelm that we all ready Done.
This Problem is the Application Varient of the Best Time to Buy and Sell Stock II,
Just Only Modified the SOme Line of the Code
In Recursice Approch We Just Add the Cap Variable in the Recursion,
Memoization, tabluation and Space Optimization that Show the Limit of the 2, and
Modified Entire Solution on the Base of the Cap Variable
Now Here is the Question Why we:-
the Answe is Any transaction Whern Counted Completed, When we Buy and Sell the Stock And Gain Some Profit, So we Need to Change the Cap Value, and When we Not Buy and Sell the Stock, So we Not Need to Change the Cap Value. Buy Stock + Shell Stock = Count 1 Transaction
Now Remaining Entire 👇 Process will Be Same As Like in DPL36
Every day, we will have 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.
Recursice Approch
Again here we follow the out Standerad Patter that we learned in the Dynamic Programming Introduction. How to Write Recursion
Steps to form the Recursive SolutionStep 1: Express the problem in terms of indexes.
Step 2: Try out all possible choices at a given index.
Every day, we have two choices
If we can buy the stock on a particular day, we have two options
Case 2: When buy == 1, we can sell the stock.
Agin we have If we can buy the stock on a particular day, we have two options
The figure below gives us the Summary
Step 3: Return the Maximum
Base Cases
class Solution: def RecursionApproch(self, prices,index,n,decisionFlag,transactionLimit): # Base Case if index == n or transactionLimit == 0: return 0 maxProfit = 0 if decisionFlag == 1: # as similar to take and notTake Concepts buyTheStock = -prices[index] + self.RecursionApproch(prices,index+1,n,0,transactionLimit) notBuyTheStock = 0 + self.RecursionApproch(prices,index+1,n,1,transactionLimit) maxProfit = max(buyTheStock,notBuyTheStock) else: shellTheStock = prices[index] + self.RecursionApproch(prices, index + 1, n, 1,transactionLimit-1) notSellTheStock = 0 + self.RecursionApproch(prices, index + 1, n, 0,transactionLimit) maxProfit = max(shellTheStock, notSellTheStock) return maxProfit def maxProfit(self, prices): n = len(prices) decisionFlag = 1 transactionLimit = 2 return self.RecursionApproch(prices,0,n,decisionFlag,transactionLimit) ans = Solution() prices = [3,3,5,0,0,3,1,4] print(ans.maxProfit(prices))
Time & Space Complexity
Time Complexity: O(2^N)Reason: Exponential Time we find out the all the Possible Path
Space Complexity: O(N)
Reason: We are using a recursion stack space(O(N))
Memoization Approch
If we Observe in the recursion tree, we will observe a many number of overlapping subproblems. Therefore the recursive solution can be memoized for to reduce the time complexity.
Steps to convert Recursive code to memoization solution:Where:-
n:- Size of the Input Array
2:- Dession Flag
3:- Cap Flag or Transaction Limit
class Solution: def MemoizationApproch(self, prices,index,n,decisionFlag,dp,transactionLimit): # Base Case if index == n or transactionLimit == 0: return 0 if dp[index][decisionFlag][transactionLimit] != -1: return dp[index][decisionFlag][transactionLimit] maxProfit = 0 if decisionFlag == 1: # as similar to take and notTake Concepts buyTheStock = -prices[index] + self.MemoizationApproch(prices,index+1,n,0,dp,transactionLimit) notBuyTheStock = 0 + self.MemoizationApproch(prices,index+1,n,1,dp,transactionLimit) maxProfit = max(buyTheStock,notBuyTheStock) else: shellTheStock = prices[index] + self.MemoizationApproch(prices, index + 1, n, 1,dp,transactionLimit-1) notSellTheStock = 0 + self.MemoizationApproch(prices, index + 1, n, 0,dp,transactionLimit) maxProfit = max(shellTheStock, notSellTheStock) dp[index][decisionFlag][transactionLimit] = maxProfit # we can also return maxProfit its also give the correct answer return dp[index][decisionFlag][transactionLimit] def maxProfit(self, prices): n = len(prices) decisionFlag = 1 transactionLimit = 2 dp = [[[-1 for _ in range(transactionLimit+1)] for _ in range(2)] for _ in range(n)] return self.MemoizationApproch(prices,0,n,decisionFlag,dp,transactionLimit) ans = Solution() prices = [3,3,5,0,0,3,1,4] print(ans.maxProfit(prices))
Time & Space Complexity
Time Complexity:O(N*2*3)Reason: There are N*2*3 states therefore at max ‘N*2*3’ new problems will be solved
Space Complexity: O(N*2*3) + O(N)
Reason: We are using a recursion stack space(O(N)) and a 3D array ( O(N*2*3)).
Tabulation Approch
Tabulation is a ‘bottom-up’ approach where we start from the base case and reach the final answer that we want and Memoization is the Top-down Approch.In Tabulation Approch We Just Creat a DP Array Same as Memoization and Simply Convert the Recurance Relation into the form of the Looping
VVVV Important Point: Tabulation Approch is Always 1-Based Indexing Always RememberSteps to convert Recursive Solution to Tabulation one.
VVVVVVV Imp Point
class Solution: def TabulationApproch(self, prices,n,dp): # Base Case # if index == n for index in range(2): for currdecisionFlag in range(3): dp[0][index][currdecisionFlag] = 0 # if transactionLimit == 0 for index in range(n): for currdecisionFlag in range(2): dp[index][currdecisionFlag][0] = 0 print(dp) # Important Note: We All Ready in our DP inilisezed with the 0, no need to write the Base # Case allready all 0, But we Write the Base Case for the Understanding the Tabulation for index in range(n-1,-1,-1): for decisionFlag in range(2): for transactionLimit in range(1,3): maxProfit = 0 if decisionFlag == 1: # as similar to take and notTake Concepts buyTheStock = -prices[index] + dp[index + 1][0][transactionLimit] notBuyTheStock = 0 + dp[index + 1][1][transactionLimit] maxProfit = max(buyTheStock,notBuyTheStock) else: shellTheStock = prices[index] + dp[index + 1][1][transactionLimit-1] notSellTheStock = 0 + dp[index + 1][0][transactionLimit] maxProfit = max(shellTheStock, notSellTheStock) dp[index][decisionFlag][transactionLimit] = maxProfit # we can also return maxProfit its also give the correct answer print(dp) return dp[0][1][2] def maxProfit(self, prices): n = len(prices) transactionLimit = 2 dp = [[[0 for _ in range(transactionLimit+1)] for _ in range(2)] for _ in range(n+1)] print(dp) return self.TabulationApproch(prices,n,dp) ans = Solution() prices = [3,3,5,0,0,3,1,4] print(ans.maxProfit(prices))
Time & Space Complexity
Time Complexity: O(N*2*3)Reason: There are three nested loops that account for O(N*2*3) complexity.
Space Complexity: O(N*2*3)
Reason: We are using an external array of size ‘N*2*3’. Stack Space is eliminated.
Space Optimization
If we closelly Observed if any Tabulation Approch we used the Some Limited Stuff like: dp[ind][buy][cap] = max(dp[ind+1][buy][cap] , max( dp[ind+1][!buy][cap])) for the finding the our ans then definetly here Spaced Optimization is Possible in that types of Problems. Always Remember
Golden Rule
class Solution: def SpaceOptimizationApproch(self, prices,n,prev,curr): # Base Case # if index == n for index in range(2): for currdecisionFlag in range(3): prev[0][currdecisionFlag] = 0 # if transactionLimit == 0 for index in range(n): for currdecisionFlag in range(2): prev[currdecisionFlag][0] = 0 print(prev) print(curr) # Important Note: We All Ready in our DP inilisezed with the 0, no need to write the Base # Case allready all 0, But we Write the Base Case for the Understanding the Tabulation for indx in range(n-1,-1,-1): for decisionFlag in range(2): for transactionLimit in range(1,3): maxProfit = 0 if decisionFlag == 1: # as similar to take and notTake Concepts buyTheStock = -prices[indx] + prev[0][transactionLimit] notBuyTheStock = 0 + prev[1][transactionLimit] maxProfit = max(buyTheStock,notBuyTheStock) else: shellTheStock = prices[indx] + prev[1][transactionLimit-1] notSellTheStock = 0 + prev[0][transactionLimit] maxProfit = max(shellTheStock, notSellTheStock) curr[decisionFlag][transactionLimit] = maxProfit prev = curr # we can also return maxProfit its also give the correct answer return prev[1][2] def maxProfit(self, prices): n = len(prices) prev = [[0 for _ in range(3)] for _ in range(2)] curr = [[0 for _ in range(3)] for _ in range(2)] return self.SpaceOptimizationApproch(prices,n,prev,curr) ans = Solution() prices = [3,3,5,0,0,3,1,4] print(ans.maxProfit(prices))
Time & Space Complexity
Time Complexity: O(N*2)Reason: There are two nested loops that account for O(N*2) complexity
Space Complexity: O(1)
Reason: We are using an external array of size ‘2’.
Most Space Optimization
If we observe very carefully, then its Possible to Optimized our Space frpm 3D DP to (N*4) DP Memoization, Tabulation and Space Optimized Possible ley's Understand How ?
What is N * 4 DP Solution
In our Solution what we do fun(cap,transcationLimit) if transcationLimit == 0
then we formed our Solution that we Discuss
So This Approch is the Completely Observation Based Approch
We know that we have the 2 transcationLimit that means 4 Options
Buy Shell Buy Shell 0 1 2 3
So here we Observed very Carefully our buy transcation will only then index % 2 == 0 (even) otherwise shell
That is the Core Concept that we used to Convert the our 3D DP Solution into the 2D DP Solution in All the Formate Memoization, Tabulation and Space Optimized
class Solution: def RecursionApproch(self, prices,index,n,transactionLimit): # Base Case if index == n or transactionLimit == 4: return 0 maxProfit = 0 if transactionLimit % 2 == 0: # as similar to take and notTake Concepts buyTheStock = -prices[index] + self.RecursionApproch(prices,index+1,n,transactionLimit+1) notBuyTheStock = 0 + self.RecursionApproch(prices,index+1,n,transactionLimit) maxProfit = max(buyTheStock,notBuyTheStock) else: shellTheStock = prices[index] + self.RecursionApproch(prices, index + 1, n ,transactionLimit+1) notSellTheStock = 0 + self.RecursionApproch(prices, index + 1, n ,transactionLimit) maxProfit = max(shellTheStock, notSellTheStock) return maxProfit def maxProfit(self, prices): n = len(prices) transactionLimit = 0 return self.RecursionApproch(prices,0,n,transactionLimit) ans = Solution() prices = [3,3,5,0,0,3,1,4] print(ans.maxProfit(prices))
class Solution: def MemoizationApproch(self, prices, index, n, dp, transactionLimit): # Base Case if index == n or transactionLimit == 4: return 0 if dp[index][transactionLimit] != -1: return dp[index][transactionLimit] maxProfit = 0 if transactionLimit % 2 == 0: # as similar to take and notTake Concepts buyTheStock = -prices[index] + self.MemoizationApproch(prices,index+1,n,dp,transactionLimit+1) notBuyTheStock = 0 + self.MemoizationApproch(prices,index+1,n,dp,transactionLimit) maxProfit = max(buyTheStock,notBuyTheStock) else: shellTheStock = prices[index] + self.MemoizationApproch(prices, index + 1, n ,dp,transactionLimit+1) notSellTheStock = 0 + self.MemoizationApproch(prices, index + 1, n ,dp,transactionLimit) maxProfit = max(shellTheStock, notSellTheStock) dp[index][transactionLimit] = maxProfit # we can also return maxProfit its also give the correct answer return dp[index][transactionLimit] def maxProfit(self, prices): n = len(prices) transactionLimit = 0 dp = [[-1 for _ in range(4)] for _ in range(n)] return self.MemoizationApproch(prices, 0, n, dp, transactionLimit) ans = Solution() prices = [3,3,5,0,0,3,1,4] print(ans.maxProfit(prices))
class Solution: def TabulationApproch(self, prices,n,dp): # Base Case # if index == n for index in range(5): dp[n][index] = 0 # if transactionLimit == 0 for index in range(n+1): dp[index][4]= 0 print(dp) # Important Note: We All Ready in our DP inilisezed with the 0, no need to write the Base # Case allready all 0, But we Write the Base Case for the Understanding the Tabulation for index in range(n-1,-1,-1): for transactionLimit in range(3,-1,-1): maxProfit = 0 if transactionLimit % 2 == 0: # as similar to take and notTake Concepts buyTheStock = -prices[index] + dp[index + 1][transactionLimit+1] notBuyTheStock = 0 + dp[index + 1][transactionLimit] maxProfit = max(buyTheStock,notBuyTheStock) else: shellTheStock = prices[index] + dp[index + 1][transactionLimit+1] notSellTheStock = 0 + dp[index + 1][transactionLimit] maxProfit = max(shellTheStock, notSellTheStock) dp[index][transactionLimit] = maxProfit # we can also return maxProfit its also give the correct answer print(dp) return dp[0][0] def maxProfit(self, prices): n = len(prices) transactionLimit = 0 dp = [[0 for _ in range(5)] for _ in range(n+1)] print(dp) return self.TabulationApproch(prices,n,dp) ans = Solution() prices = [3,3,5,0,0,3,1,4] print(ans.maxProfit(prices))
class Solution: def SpaceOptimizationApproch(self, prices,n,prev,curr): # Base Case # if index == n for index in range(5): prev[index] = 0 # if transactionLimit == 0 curr[4] = 0 print(prev) print(curr) # Important Note: We All Ready in our DP inilisezed with the 0, no need to write the Base # Case allready all 0, But we Write the Base Case for the Understanding the Tabulation for indx in range(n-1,-1,-1): for transactionLimit in range(3,-1,-1): maxProfit = 0 if transactionLimit % 2 == 0: # as similar to take and notTake Concepts buyTheStock = -prices[indx] + prev[transactionLimit+1] notBuyTheStock = 0 + prev[transactionLimit] maxProfit = max(buyTheStock,notBuyTheStock) else: shellTheStock = prices[indx] + prev[transactionLimit+1] notSellTheStock = 0 + prev[transactionLimit] maxProfit = max(shellTheStock, notSellTheStock) curr[transactionLimit] = maxProfit prev = curr # we can also return maxProfit its also give the correct answer return prev[0] def maxProfit(self, prices): n = len(prices) transactionLimit = 0 prev = [0]*5 curr = [0]*5 return self.SpaceOptimizationApproch(prices,n,prev,curr) ans = Solution() prices = [3,3,5,0,0,3,1,4] print(ans.maxProfit(prices))
Time & Space Complexity
Time Complexity: O(N*2)Reason: There are two nested loops that account for O(N*2) complexity
Space Complexity: O(5)
Reason: We are using just Size of 5(len) DP 1D Array prev and curr.
Most Space Optimization Using 4 Variables
This Approch is a popular approach on platforms like LeetCode however, it often lacks a clear intuition behind the logic. Recruiters may not be satisfied with solutions that utilize four variables without a valid and authenticated rationale. Therefore, it is advisable not to present this approach in interviews. Nevertheless, I have included this solution for the purpose of understanding and learning.
class Solution: def MostSpaceOptimizationApproch(self, prices, n): if not prices: return 0 A = -prices[0] B = float('-inf') C = float('-inf') D = float('-inf') for price in prices: A = max(A, -price) B = max(B, A + price) C = max(C, B - price) D = max(D, C + price) return D def maxProfit(self, prices): n = len(prices) return self.MostSpaceOptimizationApproch(prices, n) ans = Solution() prices = [3,3,5,0,0,3,1,4] print(ans.maxProfit(prices))
Time & Space Complexity
Time Complexity: O(N)Reason: There are two nested loops that account for O(N*2) complexity
Space Complexity: O(4)
Reason: We are using just used 4 Variable.