An integer array prices contains the price of one stock on consecutive days. Any number of transactions may be completed, and each transaction contains one purchase followed by one sale.
Only one share may be held at a time, so a sale must occur before another purchase. Buying and selling on the same day is allowed. Return the maximum total profit, with zero profit allowed when no profitable transaction exists.
Example 1
Input: prices = [7, 1, 5, 3, 6, 4]
Output: 7
Explanation: Buying at 1 and selling at 5 earns 4. Buying again at 3 and selling at 6 earns 3, producing total profit 7.
Example 2
Input: prices = [5]
Output: 0
Explanation: A single day cannot complete a profitable purchase-and-sale pair, so skipping every action keeps profit at 0.
Recursion
Every day offers one useful choice between acting and waiting. An empty hand allows a purchase or a skip, while a held share allows a sale or a hold. Both paths lead to the same smaller problem on the next day, so recursion can explore every valid trading sequence.
State solve(day, canBuy) stores the best profit available from day onward. Flag value 1 means no share is held, while flag value 0 means a sale must happen before another purchase. The public method starts with the initial call solve(0, 1) because trading begins on the first day with an empty hand.
Algorithm
Begin with
solve(0, 1)because the trading period starts on day0without a held share.Return
0after the last day because no later transaction can add profit and every pending action may be skipped.Explore a purchase by subtracting
prices[day]and moving tocanBuy = 0because a held share blocks another purchase.Explore a skip from
canBuy = 1without changing the state, since waiting preserves the right to purchase later.Explore a sale by adding
prices[day]and moving tocanBuy = 1because a completed sale permits another transaction.Explore a hold from
canBuy = 0without changing the state because a later selling price may produce a larger profit.Return the larger valid choice at every state because the best trading sequence must begin with one of the two available actions.
Dry Run
Diagram 1
Solution
#include <bits/stdc++.h>using namespace std;class Solution {private: // Finds the best profit from one trading state. long long solve( int day, int canBuy, vector<int>& prices ) { int n = prices.size(); // No profit remains after the final day. if (day == n) { return 0; } // An empty hand allows buying or waiting. if (canBuy == 1) { // Buying pays the current stock price. long long buy = -prices[day] + solve(day + 1, 0, prices); // Skipping preserves the empty-hand state. long long skip = solve(day + 1, 1, prices); // The stronger opening choice maximizes profit. return max(buy, skip); } // Selling collects the current stock price. long long sell = prices[day] + solve(day + 1, 1, prices); // Holding preserves the owned-share state. long long hold = solve(day + 1, 0, prices); // The stronger closing choice maximizes profit. return max(sell, hold); }public: // Finds the maximum profit from unlimited trades. long long maxProfit(vector<int>& prices) { // Trading starts empty-handed on the first day. return solve(0, 1, prices); }};// Driver codeint main() { vector<int> prices = {7, 1, 5, 3, 6, 4}; Solution obj; cout << obj.maxProfit(prices); return 0;}Note: Direct recursion may fail for large input values. Repeated subproblems create exponential work, so an online judge may report Time Limit Exceeded.
Complexity Analysis
Time Complexity: O(2N), where N is the number of days, because two choices can branch from each day in the worst-case recursion tree.
Space Complexity: O(N), because the deepest recursive path can contain one active call for each day.
Memoization
Recursive paths repeatedly reach the same pair of day and holding status. Recomputing an identical state cannot improve the answer, so a dp table can save every completed result and return cached profit during later visits.
The recursive choices and state meaning remain unchanged. Table entry dp[day][canBuy] stores the answer for one state, turning the exponential tree into a collection of at most two states per day.
Algorithm
Begin with a
dptable ofnrows and two columns filled with-1because every valid state has a non-negative answer.Start from
solve(0, 1)because no share is held before the first trading day.Return
0after dayn - 1because no future action remains available.Return
dp[day][canBuy]when a stored value exists, preventing repeated evaluation of the same trading state.Compare buying with skipping for
canBuy = 1because an empty hand permits either paying the current price or waiting.Compare selling with holding for
canBuy = 0because a held share permits either collecting the current price or waiting.Store and return the larger choice in
dp[day][canBuy]so every later visit receives the best future profit immediately.
Dry Run
Diagram 1
Solution
#include <bits/stdc++.h>using namespace std;class Solution {private: // Finds one state and caches the best profit. long long solve( int day, int canBuy, vector<int>& prices, vector<vector<long long>>& dp ) { int n = prices.size(); // No profit remains after the final day. if (day == n) { return 0; } // A stored state avoids repeated trading paths. if (dp[day][canBuy] != -1) { return dp[day][canBuy]; } long long bestProfit; // An empty hand allows buying or waiting. if (canBuy == 1) { // Buying pays the current stock price. long long buy = -prices[day] + solve(day + 1, 0, prices, dp); // Skipping preserves the empty-hand state. long long skip = solve(day + 1, 1, prices, dp); // The stronger opening choice enters the cache. bestProfit = max(buy, skip); } else { // Selling collects the current stock price. long long sell = prices[day] + solve(day + 1, 1, prices, dp); // Holding preserves the owned-share state. long long hold = solve(day + 1, 0, prices, dp); // The stronger closing choice enters the cache. bestProfit = max(sell, hold); } // Caching preserves the best result for reuse. dp[day][canBuy] = bestProfit; return dp[day][canBuy]; }public: // Finds the maximum profit from unlimited trades. long long maxProfit(vector<int>& prices) { int n = prices.size(); // Minus one marks every uncalculated state. vector<vector<long long>> dp( n, vector<long long>(2, -1) ); // Trading starts empty-handed on the first day. return solve(0, 1, prices, dp); }};// Driver codeint main() { vector<int> prices = {7, 1, 5, 3, 6, 4}; Solution obj; cout << obj.maxProfit(prices); return 0;}Complexity Analysis
Time Complexity: O(N), where N is the number of days, because at most two states are solved once for each day.
Space Complexity: O(N), because the dp table stores two states for each of the N days, and the recursion stack can contain up to N calls.
Tabulation
Memoization still depends on recursive calls. A bottom-up dp table can store the same two states directly, starting from the known answer after the last day and moving backward toward day 0.
Row dp[day] depends only on row dp[day + 1], so reverse iteration guarantees every required future value is ready. Entry dp[0][1] remains the complete answer because trading begins on day 0 with permission to buy.
Algorithm
Begin with a
dptable containingn + 1rows and two columns, so rowncan represent the period after all prices.Keep both values in row
nat0because no action after the final day can earn profit.Move from day
n - 1toward day0so every transition can read the already-computed next-day states.Set
dp[day][1]to the larger value from buying or skipping, because an empty hand allows either choice.Set
dp[day][0]to the larger value from selling or holding, because a held share blocks another purchase.Preserve the same purchase subtraction and sale addition used by recursion so the state meaning never changes.
Return
dp[0][1]because the full trading period starts on day0without a held share.
Dry Run
Best Time to Buy and Sell Stock II Tabulation
Solution
#include <bits/stdc++.h>using namespace std;class Solution {public: // Finds the maximum profit with bottom-up states. long long maxProfit(vector<int>& prices) { int n = prices.size(); // Row n represents the period after all days. vector<vector<long long>> dp( n + 1, vector<long long>(2, 0) ); // Reverse order makes next-day states available. for (int day = n - 1; day >= 0; day--) { // Buying or skipping starts from an empty hand. long long buy = -prices[day] + dp[day + 1][0]; long long skip = dp[day + 1][1]; // The stronger empty-hand choice fills the state. dp[day][1] = max(buy, skip); // Selling or holding starts with an owned share. long long sell = prices[day] + dp[day + 1][1]; long long hold = dp[day + 1][0]; // The stronger owned-share choice fills the state. dp[day][0] = max(sell, hold); } // The first day starts with permission to buy. return dp[0][1]; }};// Driver codeint main() { vector<int> prices = {7, 1, 5, 3, 6, 4}; Solution obj; cout << obj.maxProfit(prices); return 0;}Complexity Analysis
Time Complexity: O(N), where N is the number of days, because two constant-time states are filled for each day.
Space Complexity: O(N), because the dp table stores two profit values for each of the N days, and no recursion stack is used.
Space Optimization
Each tabulation row reads only the next row. Older rows never enter another transition, so two next-day values and two current-day values can replace the full dp table.
Variables aheadCanBuy and aheadMustSell represent the next day. Both current values must be calculated before either ahead value changes, because both transitions depend on the same untouched next-day pair. The completed current pair then shifts forward for the following iteration.
Algorithm
Begin with
aheadCanBuy = 0andaheadMustSell = 0because no profit remains after the last day in either state.Move backward from day
n - 1so the retained ahead values always represent dayday + 1.Calculate
currentCanBuyfrom buying or skipping so a purchase readsaheadMustSelland a skip readsaheadCanBuy.Calculate
currentMustSellfrom selling or holding so a sale readsaheadCanBuyand a hold readsaheadMustSell.Finish both current calculations before shifting values because both states require the same unchanged next-day pair.
Shift
currentCanBuyandcurrentMustSellinto the ahead variables so the next iteration receives the newly completed row.Return
aheadCanBuyafter day0because trading begins with an empty hand and permission to buy.
Dry Run
Best Time to Buy and Sell Stock II Space Optimization
Solution
#include <bits/stdc++.h>using namespace std;class Solution {public: // Finds the maximum profit with rolling states. long long maxProfit(vector<int>& prices) { int n = prices.size(); // Both states earn zero after the final day. long long aheadCanBuy = 0; long long aheadMustSell = 0; // Reverse order keeps the next-day pair available. for (int day = n - 1; day >= 0; day--) { // The current buy state compares both choices. long long currentCanBuy = max( -prices[day] + aheadMustSell, aheadCanBuy ); // The current sell state compares both choices. long long currentMustSell = max( prices[day] + aheadCanBuy, aheadMustSell ); // Shift both current values into the ahead pair. aheadCanBuy = currentCanBuy; aheadMustSell = currentMustSell; } // The first day starts with permission to buy. return aheadCanBuy; }};// Driver codeint main() { vector<int> prices = {7, 1, 5, 3, 6, 4}; Solution obj; cout << obj.maxProfit(prices); return 0;}Complexity Analysis
Time Complexity: O(N), where N is the number of days, because two constant-time state transitions are calculated for each day.
Space Complexity: O(1), because four profit variables replace the full dp table, and no recursion stack is used.
Interview follow-up Questions
No. Skipping every day produces profit 0, so decreasing or constant prices never force a loss.
Be the first to add a comment.