Bird
Raised Fist0
Interview Prepdp-linear-fibonaccihardAmazonGoogle

Best Time - 2 Transactions

Choose your preparation mode4 modes available
🎯
Best Time - 2 Transactions
hardDPAmazonGoogle

Imagine you are an investor who can buy and sell a stock at most twice to maximize profit. How do you decide the best days to buy and sell?

💡 This problem is a classic example of dynamic programming where beginners often struggle to model multiple transactions and states. Understanding how to track profits with limited transactions is key.
📋
Problem Statement

Given an array prices where prices[i] is the price of a given stock on the i-th day, find the maximum profit you can achieve. You may complete at most two transactions. Note: You may not engage in multiple transactions simultaneously (i.e., you must sell the stock before you buy again). Input: An integer array prices. Output: An integer representing the maximum profit.

1 ≤ prices.length ≤ 10^50 ≤ prices[i] ≤ 10^5
💡
Example
Input"[3,3,5,0,0,3,1,4]"
Output6

Buy on day 4 (price=0), sell on day 6 (price=3), profit=3. Then buy on day 7 (price=1), sell on day 8 (price=4), profit=3. Total profit=6.

  • prices = [1] → 0 (only one day, no transaction possible)
  • prices = [5,4,3,2,1] → 0 (prices decreasing, no profit possible)
  • prices = [1,2,3,4,5] → 4 (best to buy at day 1 and sell at day 5, only one transaction needed)
  • prices = [1,2,4,2,5,7,2,4,9,0] → 13 (complex case with multiple peaks and valleys)
🔁
Why DP?
Why greedy fails:

A greedy approach that picks local maxima and minima fails because it might miss better global profits by splitting transactions differently. For example, buying low and selling high twice might yield more profit than one big transaction.

DP state:

dp[i][j] represents the maximum profit achievable up to day i with at most j transactions completed.

Recurrence:dp[i][k] = max(dp[i-1][k], max_{j < i}(prices[i] - prices[j] + dp[j][k-1]))

For each day i and transaction count k, either do no transaction today (carry over previous profit) or sell today after buying on some earlier day j plus profit from k-1 transactions before day j.

⚠️
Common Mistakes
Not handling the 'at most two transactions' constraint correctly

Incorrect profit calculation, possibly overcounting transactions

✅ Use DP states that track the number of transactions completed

Forgetting to update maxDiff correctly in tabulation

DP table values become incorrect, leading to wrong answers

✅ Update maxDiff after dp[i][k-1] is computed for each day

Iterating forwards in space-optimized DP causing overwritten states

Incorrect profit values due to using updated states prematurely

✅ Iterate backwards over transactions to preserve previous states

Not handling edge cases like empty prices array

Runtime errors or incorrect output

✅ Add checks for empty or minimal input arrays

Using floating point instead of integers for prices

Precision errors in profit calculations

✅ Use integer types as prices are integers

🧠
Brute Force (Nested Loops)
💡 This approach tries all possible pairs of buy and sell days for two transactions, helping beginners understand the problem's combinatorial nature before optimization.

Intuition

Try every possible pair of transactions by checking all buy-sell pairs twice and pick the maximum profit.

Algorithm

  1. Enumerate all pairs of buy and sell days for the first transaction.
  2. For each first transaction, enumerate all pairs of buy and sell days for the second transaction after the first sell day.
  3. Calculate total profit for these two transactions.
  4. Keep track of the maximum profit found.
💡 This approach is straightforward but inefficient because it checks every possible transaction pair explicitly.
Recurrence:Not applicable for brute force
</>
Code
def maxProfit(prices):
    n = len(prices)
    max_profit = 0
    for i in range(n):
        for j in range(i + 1, n):
            profit1 = prices[j] - prices[i]
            if profit1 < 0:
                profit1 = 0
            for k in range(j + 1, n):
                for l in range(k + 1, n):
                    profit2 = prices[l] - prices[k]
                    if profit2 < 0:
                        profit2 = 0
                    max_profit = max(max_profit, profit1 + profit2)
    return max_profit

# Driver code
if __name__ == '__main__':
    prices = [3,3,5,0,0,3,1,4]
    print(maxProfit(prices))  # Expected output: 6
Line Notes
for i in range(n)Outer loop picks the first buy day for the first transaction to explore all possible start days.
for j in range(i + 1, n)Inner loop picks the sell day for the first transaction ensuring sell day is after buy day.
profit1 = prices[j] - prices[i]Calculate profit for the first transaction; if negative, set to zero as no profit.
for k in range(j + 1, n)Start second transaction after the first sell day to avoid overlapping transactions.
for l in range(k + 1, n)Pick sell day for the second transaction ensuring valid buy-sell order.
profit2 = prices[l] - prices[k]Calculate profit for the second transaction; if negative, set to zero.
max_profit = max(max_profit, profit1 + profit2)Update max profit if current two transactions yield better profit.
public class Solution {
    public static int maxProfit(int[] prices) {
        int n = prices.length;
        int maxProfit = 0;
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                int profit1 = prices[j] - prices[i];
                if (profit1 < 0) profit1 = 0;
                for (int k = j + 1; k < n; k++) {
                    for (int l = k + 1; l < n; l++) {
                        int profit2 = prices[l] - prices[k];
                        if (profit2 < 0) profit2 = 0;
                        maxProfit = Math.max(maxProfit, profit1 + profit2);
                    }
                }
            }
        }
        return maxProfit;
    }

    public static void main(String[] args) {
        int[] prices = {3,3,5,0,0,3,1,4};
        System.out.println(maxProfit(prices)); // Expected output: 6
    }
}
Line Notes
for (int i = 0; i < n; i++)Outer loop selects first buy day to try all possibilities.
for (int j = i + 1; j < n; j++)Inner loop selects first sell day after buy day.
int profit1 = prices[j] - prices[i];Calculate profit for first transaction; zero if negative.
for (int k = j + 1; k < n; k++)Start second transaction after first sell day.
for (int l = k + 1; l < n; l++)Select second sell day after second buy day.
maxProfit = Math.max(maxProfit, profit1 + profit2);Update max profit if current combination is better.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int maxProfit(vector<int>& prices) {
    int n = prices.size();
    int maxProfit = 0;
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {
            int profit1 = prices[j] - prices[i];
            if (profit1 < 0) profit1 = 0;
            for (int k = j + 1; k < n; k++) {
                for (int l = k + 1; l < n; l++) {
                    int profit2 = prices[l] - prices[k];
                    if (profit2 < 0) profit2 = 0;
                    maxProfit = max(maxProfit, profit1 + profit2);
                }
            }
        }
    }
    return maxProfit;
}

int main() {
    vector<int> prices = {3,3,5,0,0,3,1,4};
    cout << maxProfit(prices) << endl; // Expected output: 6
    return 0;
}
Line Notes
for (int i = 0; i < n; i++)Select first buy day to try all options.
for (int j = i + 1; j < n; j++)Select first sell day after buy day.
int profit1 = prices[j] - prices[i];Calculate profit for first transaction; zero if negative.
for (int k = j + 1; k < n; k++)Start second transaction after first sell day.
for (int l = k + 1; l < n; l++)Select second sell day after second buy day.
maxProfit = max(maxProfit, profit1 + profit2);Update max profit if better combination found.
function maxProfit(prices) {
    const n = prices.length;
    let maxProfit = 0;
    for (let i = 0; i < n; i++) {
        for (let j = i + 1; j < n; j++) {
            let profit1 = prices[j] - prices[i];
            if (profit1 < 0) profit1 = 0;
            for (let k = j + 1; k < n; k++) {
                for (let l = k + 1; l < n; l++) {
                    let profit2 = prices[l] - prices[k];
                    if (profit2 < 0) profit2 = 0;
                    maxProfit = Math.max(maxProfit, profit1 + profit2);
                }
            }
        }
    }
    return maxProfit;
}

// Test
console.log(maxProfit([3,3,5,0,0,3,1,4])); // Expected output: 6
Line Notes
for (let i = 0; i < n; i++)Outer loop picks first buy day to explore all possibilities.
for (let j = i + 1; j < n; j++)Inner loop picks first sell day after buy day.
let profit1 = prices[j] - prices[i];Calculate profit for first transaction; zero if negative.
for (let k = j + 1; k < n; k++)Start second transaction after first sell day.
for (let l = k + 1; l < n; l++)Select second sell day after second buy day.
maxProfit = Math.max(maxProfit, profit1 + profit2);Update max profit if current two transactions yield better profit.
⏱
Complexity
TimeO(n^4)
SpaceO(1)

Four nested loops over n days lead to O(n^4) time complexity.

💡 For n=10, this means 10,000 operations; for n=100, 100 million operations, which is impractical.
Interview Verdict: TLE

This approach is too slow for large inputs but helps understand the problem before optimizing.

🧠
Memoization with Recursion
💡 This approach uses recursion with memoization to avoid recomputing overlapping subproblems, improving efficiency over brute force.

Intuition

Use recursion to explore buy/sell decisions day by day, caching results to avoid repeated work.

Algorithm

  1. Define a recursive function with parameters: current day, number of transactions left, and holding state.
  2. At each day, decide to buy, sell, or skip based on current state.
  3. Use memoization to store results for states to avoid recomputation.
  4. Return the maximum profit achievable.
💡 This approach is easier to implement than tabulation but still may be slow for very large inputs.
Recurrence:dp(i, k, holding) = max( dp(i+1, k, holding), if holding == 0: -prices[i] + dp(i+1, k, 1), else: prices[i] + dp(i+1, k-1, 0) )
</>
Code
def maxProfit(prices):
    from functools import lru_cache
    n = len(prices)

    @lru_cache(None)
    def dfs(i, k, holding):
        if i == n or k == 0:
            return 0
        do_nothing = dfs(i+1, k, holding)
        if holding == 0:
            buy = dfs(i+1, k, 1) - prices[i]
            return max(do_nothing, buy)
        else:
            sell = dfs(i+1, k-1, 0) + prices[i]
            return max(do_nothing, sell)

    return dfs(0, 2, 0)

# Driver code
if __name__ == '__main__':
    prices = [3,3,5,0,0,3,1,4]
    print(maxProfit(prices))  # Expected output: 6
Line Notes
@lru_cache(None)Memoize results to avoid recomputing states, improving efficiency.
if i == n or k == 0:Base case: no days left or no transactions left means no profit.
do_nothing = dfs(i+1, k, holding)Option to skip current day without any action.
if holding == 0:If not holding stock, option to buy today.
buy = dfs(i+1, k, 1) - prices[i]Buy stock today, reducing cash by price.
sell = dfs(i+1, k-1, 0) + prices[i]Sell stock today, gain profit and reduce transactions left.
import java.util.*;

public class Solution {
    private static int[] prices;
    private static Integer[][][] memo;

    public static int dfs(int i, int k, int holding) {
        if (i == prices.length || k == 0) return 0;
        if (memo[i][k][holding] != null) return memo[i][k][holding];
        int doNothing = dfs(i + 1, k, holding);
        int result;
        if (holding == 0) {
            int buy = dfs(i + 1, k, 1) - prices[i];
            result = Math.max(doNothing, buy);
        } else {
            int sell = dfs(i + 1, k - 1, 0) + prices[i];
            result = Math.max(doNothing, sell);
        }
        memo[i][k][holding] = result;
        return result;
    }

    public static int maxProfit(int[] p) {
        prices = p;
        memo = new Integer[prices.length + 1][3][2];
        return dfs(0, 2, 0);
    }

    public static void main(String[] args) {
        int[] prices = {3,3,5,0,0,3,1,4};
        System.out.println(maxProfit(prices)); // Expected output: 6
    }
}
Line Notes
if (i == prices.length || k == 0) return 0;Base case: no days or no transactions left means no profit.
if (memo[i][k][holding] != null) return memo[i][k][holding];Return cached result if available to avoid recomputation.
int doNothing = dfs(i + 1, k, holding);Option to skip current day.
if (holding == 0)If not holding, option to buy today.
int buy = dfs(i + 1, k, 1) - prices[i];Buy stock today, reducing cash.
int sell = dfs(i + 1, k - 1, 0) + prices[i];Sell stock today, gain profit and reduce transactions.
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;

int prices[100005];
int memo[100005][3][2];
int n;

int dfs(int i, int k, int holding) {
    if (i == n || k == 0) return 0;
    if (memo[i][k][holding] != -1) return memo[i][k][holding];
    int doNothing = dfs(i + 1, k, holding);
    int result;
    if (holding == 0) {
        int buy = dfs(i + 1, k, 1) - prices[i];
        result = max(doNothing, buy);
    } else {
        int sell = dfs(i + 1, k - 1, 0) + prices[i];
        result = max(doNothing, sell);
    }
    memo[i][k][holding] = result;
    return result;
}

int maxProfit(vector<int>& p) {
    n = p.size();
    for (int i = 0; i <= n; i++)
        for (int j = 0; j <= 2; j++)
            memo[i][j][0] = memo[i][j][1] = -1;
    for (int i = 0; i < n; i++) prices[i] = p[i];
    return dfs(0, 2, 0);
}

int main() {
    vector<int> prices = {3,3,5,0,0,3,1,4};
    cout << maxProfit(prices) << endl; // Expected output: 6
    return 0;
}
Line Notes
if (i == n || k == 0) return 0;Base case: no days or no transactions left means no profit.
if (memo[i][k][holding] != -1) return memo[i][k][holding];Return cached result if computed to avoid recomputation.
int doNothing = dfs(i + 1, k, holding);Option to skip current day.
if (holding == 0)If not holding, option to buy today.
int buy = dfs(i + 1, k, 1) - prices[i];Buy stock today, reducing cash.
int sell = dfs(i + 1, k - 1, 0) + prices[i];Sell stock today, gain profit and reduce transactions.
function maxProfit(prices) {
    const n = prices.length;
    const memo = new Array(n).fill(0).map(() => new Array(3).fill(0).map(() => new Array(2).fill(null)));

    function dfs(i, k, holding) {
        if (i === n || k === 0) return 0;
        if (memo[i][k][holding] !== null) return memo[i][k][holding];
        const doNothing = dfs(i + 1, k, holding);
        let result;
        if (holding === 0) {
            const buy = dfs(i + 1, k, 1) - prices[i];
            result = Math.max(doNothing, buy);
        } else {
            const sell = dfs(i + 1, k - 1, 0) + prices[i];
            result = Math.max(doNothing, sell);
        }
        memo[i][k][holding] = result;
        return result;
    }

    return dfs(0, 2, 0);
}

// Test
console.log(maxProfit([3,3,5,0,0,3,1,4])); // Expected output: 6
Line Notes
if (i === n || k === 0) return 0;Base case: no days or no transactions left means no profit.
if (memo[i][k][holding] !== null) return memo[i][k][holding];Return cached result if available to avoid recomputation.
const doNothing = dfs(i + 1, k, holding);Option to skip current day.
if (holding === 0)If not holding, option to buy today.
const buy = dfs(i + 1, k, 1) - prices[i];Buy stock today, reducing cash.
const sell = dfs(i + 1, k - 1, 0) + prices[i];Sell stock today, gain profit and reduce transactions.
⏱
Complexity
TimeO(n * k * 2) = O(n)
SpaceO(n * k * 2) for memoization stack

Each day, transaction count, and holding state is computed once and cached.

💡 For n=10^5, this is feasible with memoization, unlike brute force.
Interview Verdict: Accepted

This approach is efficient enough for large inputs and demonstrates DP with memoization well.

🧠
Tabulation (Bottom-Up DP)
💡 This approach uses iterative DP to build the solution bottom-up, which is often preferred in interviews for clarity and efficiency.

Intuition

Build a DP table where dp[i][k] stores max profit up to day i with at most k transactions, iteratively computing values.

Algorithm

  1. Initialize a 2D dp array with dimensions (days) x (transactions+1).
  2. Iterate over transactions from 1 to 2.
  3. For each transaction, track the maximum balance after buying (maxDiff).
  4. Iterate over days, update dp[i][k] as max of dp[i-1][k] and prices[i] + maxDiff.
  5. Return dp[n-1][2] as the answer.
💡 This approach avoids recursion and uses a table to store intermediate results for clarity and speed.
Recurrence:dp[i][k] = max(dp[i-1][k], prices[i] + maxDiff), where maxDiff = max(maxDiff, dp[j][k-1] - prices[j]) for j < i
</>
Code
def maxProfit(prices):
    n = len(prices)
    if n == 0:
        return 0
    dp = [[0] * 3 for _ in range(n)]
    for k in range(1, 3):
        maxDiff = -prices[0]
        for i in range(1, n):
            dp[i][k] = max(dp[i-1][k], prices[i] + maxDiff)
            maxDiff = max(maxDiff, dp[i][k-1] - prices[i])
    return dp[-1][2]

# Driver code
if __name__ == '__main__':
    prices = [3,3,5,0,0,3,1,4]
    print(maxProfit(prices))  # Expected output: 6
Line Notes
dp = [[0] * 3 for _ in range(n)]Initialize DP table with 3 columns for 0,1,2 transactions to store max profits.
for k in range(1, 3)Iterate over transaction counts 1 and 2 to build solutions incrementally.
maxDiff = -prices[0]Track max difference of dp[i][k-1] - prices[i] representing best buying opportunity.
dp[i][k] = max(dp[i-1][k], prices[i] + maxDiff)Max profit either skipping today or selling today after buying at best previous day.
maxDiff = max(maxDiff, dp[i][k-1] - prices[i])Update maxDiff for next iteration to reflect best buy day.
public class Solution {
    public static int maxProfit(int[] prices) {
        int n = prices.length;
        if (n == 0) return 0;
        int[][] dp = new int[n][3];
        for (int k = 1; k <= 2; k++) {
            int maxDiff = -prices[0];
            for (int i = 1; i < n; i++) {
                dp[i][k] = Math.max(dp[i - 1][k], prices[i] + maxDiff);
                maxDiff = Math.max(maxDiff, dp[i][k - 1] - prices[i]);
            }
        }
        return dp[n - 1][2];
    }

    public static void main(String[] args) {
        int[] prices = {3,3,5,0,0,3,1,4};
        System.out.println(maxProfit(prices)); // Expected output: 6
    }
}
Line Notes
int[][] dp = new int[n][3];DP table for days and transactions to store max profits.
for (int k = 1; k <= 2; k++)Loop over transaction counts 1 and 2.
int maxDiff = -prices[0];Initialize maxDiff for buying opportunity.
dp[i][k] = Math.max(dp[i - 1][k], prices[i] + maxDiff);Max profit by skipping or selling today.
maxDiff = Math.max(maxDiff, dp[i][k - 1] - prices[i]);Update maxDiff for next day.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int maxProfit(vector<int>& prices) {
    int n = prices.size();
    if (n == 0) return 0;
    vector<vector<int>> dp(n, vector<int>(3, 0));
    for (int k = 1; k <= 2; k++) {
        int maxDiff = -prices[0];
        for (int i = 1; i < n; i++) {
            dp[i][k] = max(dp[i - 1][k], prices[i] + maxDiff);
            maxDiff = max(maxDiff, dp[i][k - 1] - prices[i]);
        }
    }
    return dp[n - 1][2];
}

int main() {
    vector<int> prices = {3,3,5,0,0,3,1,4};
    cout << maxProfit(prices) << endl; // Expected output: 6
    return 0;
}
Line Notes
vector<vector<int>> dp(n, vector<int>(3, 0));DP table initialization for days and transactions.
for (int k = 1; k <= 2; k++)Loop over transaction counts 1 and 2.
int maxDiff = -prices[0];Initialize maxDiff for buying opportunity.
dp[i][k] = max(dp[i - 1][k], prices[i] + maxDiff);Max profit by skipping or selling today.
maxDiff = max(maxDiff, dp[i][k - 1] - prices[i]);Update maxDiff for next iteration.
function maxProfit(prices) {
    const n = prices.length;
    if (n === 0) return 0;
    const dp = Array.from({ length: n }, () => Array(3).fill(0));
    for (let k = 1; k <= 2; k++) {
        let maxDiff = -prices[0];
        for (let i = 1; i < n; i++) {
            dp[i][k] = Math.max(dp[i - 1][k], prices[i] + maxDiff);
            maxDiff = Math.max(maxDiff, dp[i][k - 1] - prices[i]);
        }
    }
    return dp[n - 1][2];
}

// Test
console.log(maxProfit([3,3,5,0,0,3,1,4])); // Expected output: 6
Line Notes
const dp = Array.from({ length: n }, () => Array(3).fill(0));Initialize DP table for days and transactions.
for (let k = 1; k <= 2; k++)Loop over transaction counts 1 and 2.
let maxDiff = -prices[0];Initialize maxDiff for buying opportunity.
dp[i][k] = Math.max(dp[i - 1][k], prices[i] + maxDiff);Max profit by skipping or selling today.
maxDiff = Math.max(maxDiff, dp[i][k - 1] - prices[i]);Update maxDiff for next iteration.
⏱
Complexity
TimeO(n * k) = O(n)
SpaceO(n * k) = O(n)

Two nested loops over days and transactions, each linear in n and k=2.

💡 For n=10^5, this is efficient and practical.
Interview Verdict: Accepted

This is the standard DP solution for this problem and is interview-ready.

🧠
Space Optimized DP
💡 This approach reduces space complexity by using 1D arrays and iterating backwards to preserve previous states.

Intuition

Use two arrays to track profits for each transaction count, updating in reverse to avoid overwriting needed values.

Algorithm

  1. Initialize two arrays for profits and maxDiff for each transaction count.
  2. Iterate over days updating profits and maxDiff in reverse transaction order.
  3. Update maxDiff using previous profits and current prices.
  4. Return the profit for 2 transactions after processing all days.
💡 This approach saves memory while maintaining the logic of the tabulation method.
Recurrence:profit[k] = max(profit[k], prices[i] + maxDiff[k]) maxDiff[k] = max(maxDiff[k], profit[k-1] - prices[i])
</>
Code
def maxProfit(prices):
    n = len(prices)
    if n == 0:
        return 0
    profit = [0] * 3
    maxDiff = [-prices[0]] * 3
    for i in range(1, n):
        for k in range(2, 0, -1):
            profit[k] = max(profit[k], prices[i] + maxDiff[k])
            maxDiff[k] = max(maxDiff[k], profit[k-1] - prices[i])
    return profit[2]

# Driver code
if __name__ == '__main__':
    prices = [3,3,5,0,0,3,1,4]
    print(maxProfit(prices))  # Expected output: 6
Line Notes
profit = [0] * 3Initialize profit array for 0,1,2 transactions to track max profits.
maxDiff = [-prices[0]] * 3Initialize maxDiff for buying opportunities with negative first price.
for k in range(2, 0, -1)Iterate transactions backwards to avoid overwriting needed previous states.
profit[k] = max(profit[k], prices[i] + maxDiff[k])Update profit by selling today or skipping.
maxDiff[k] = max(maxDiff[k], profit[k-1] - prices[i])Update maxDiff for buying at best previous profit.
public class Solution {
    public static int maxProfit(int[] prices) {
        int n = prices.length;
        if (n == 0) return 0;
        int[] profit = new int[3];
        int[] maxDiff = new int[3];
        Arrays.fill(maxDiff, -prices[0]);
        for (int i = 1; i < n; i++) {
            for (int k = 2; k >= 1; k--) {
                profit[k] = Math.max(profit[k], prices[i] + maxDiff[k]);
                maxDiff[k] = Math.max(maxDiff[k], profit[k - 1] - prices[i]);
            }
        }
        return profit[2];
    }

    public static void main(String[] args) {
        int[] prices = {3,3,5,0,0,3,1,4};
        System.out.println(maxProfit(prices)); // Expected output: 6
    }
}
Line Notes
int[] profit = new int[3];Profit array for transactions to track max profits.
int[] maxDiff = new int[3];maxDiff array for buying opportunities.
Arrays.fill(maxDiff, -prices[0]);Initialize maxDiff with negative first price.
for (int k = 2; k >= 1; k--)Iterate backwards to preserve previous states.
profit[k] = Math.max(profit[k], prices[i] + maxDiff[k]);Update profit by selling or skipping.
maxDiff[k] = Math.max(maxDiff[k], profit[k - 1] - prices[i]);Update maxDiff for buying.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int maxProfit(vector<int>& prices) {
    int n = prices.size();
    if (n == 0) return 0;
    vector<int> profit(3, 0);
    vector<int> maxDiff(3, -prices[0]);
    for (int i = 1; i < n; i++) {
        for (int k = 2; k >= 1; k--) {
            profit[k] = max(profit[k], prices[i] + maxDiff[k]);
            maxDiff[k] = max(maxDiff[k], profit[k - 1] - prices[i]);
        }
    }
    return profit[2];
}

int main() {
    vector<int> prices = {3,3,5,0,0,3,1,4};
    cout << maxProfit(prices) << endl; // Expected output: 6
    return 0;
}
Line Notes
vector<int> profit(3, 0);Profit array initialization for transactions.
vector<int> maxDiff(3, -prices[0]);maxDiff initialization for buying opportunities.
for (int k = 2; k >= 1; k--)Iterate backwards to avoid overwriting needed states.
profit[k] = max(profit[k], prices[i] + maxDiff[k]);Update profit by selling or skipping.
maxDiff[k] = max(maxDiff[k], profit[k - 1] - prices[i]);Update maxDiff for buying.
function maxProfit(prices) {
    const n = prices.length;
    if (n === 0) return 0;
    const profit = [0, 0, 0];
    const maxDiff = [-prices[0], -prices[0], -prices[0]];
    for (let i = 1; i < n; i++) {
        for (let k = 2; k >= 1; k--) {
            profit[k] = Math.max(profit[k], prices[i] + maxDiff[k]);
            maxDiff[k] = Math.max(maxDiff[k], profit[k - 1] - prices[i]);
        }
    }
    return profit[2];
}

// Test
console.log(maxProfit([3,3,5,0,0,3,1,4])); // Expected output: 6
Line Notes
const profit = [0, 0, 0];Profit array initialization for transactions.
const maxDiff = [-prices[0], -prices[0], -prices[0]];maxDiff initialization for buying opportunities.
for (let k = 2; k >= 1; k--)Iterate backwards to preserve previous states.
profit[k] = Math.max(profit[k], prices[i] + maxDiff[k]);Update profit by selling or skipping.
maxDiff[k] = Math.max(maxDiff[k], profit[k - 1] - prices[i]);Update maxDiff for buying.
⏱
Complexity
TimeO(n * k) = O(n)
SpaceO(k) = O(1)

Only two arrays of size k=3 are used, iterating over n days.

💡 This is the most memory-efficient DP solution, ideal for large inputs.
Interview Verdict: Accepted

This approach is optimal in both time and space, demonstrating advanced DP optimization.

📊
All Approaches - One-Glance Tradeoffs
💡 Use the tabulation or space-optimized DP in 95% of interviews for best balance of clarity and efficiency.
ApproachTimeSpaceStack RiskReconstructUse In Interview
1. Brute ForceO(n^4)O(1)NoYesMention only - never code
2. MemoizationO(n * k * 2)O(n * k * 2)Possible (deep recursion)YesGood to explain, sometimes code
3. TabulationO(n * k)O(n * k)NoYesPreferred approach to code
4. Space OptimizedO(n * k)O(k)NoNo (without extra bookkeeping)Mention or code if asked for optimization
💼
Interview Strategy
💡 Use this guide to understand the problem deeply, practice all approaches, and prepare to explain your reasoning clearly in interviews.

How to Present

Clarify the problem constraints and confirm understanding.Explain the brute force approach to show problem comprehension.Discuss overlapping subproblems and introduce memoization.Present the tabulation DP solution as the optimal approach.Mention space optimization if time permits.Write clean, tested code and analyze complexity.Test edge cases and explain your reasoning.

Time Allocation

Clarify: 3min → Approach: 7min → Code: 10min → Test: 5min. Total ~25min

What the Interviewer Tests

Interviewer tests your understanding of DP states, transitions, optimization, and ability to write clean, efficient code.

Common Follow-ups

  • What if k (transactions) is very large? → Use quick solution for unlimited transactions.
  • Can you optimize space complexity? → Use 1D DP arrays with backward iteration.
  • How to handle transaction fees? → Modify state transitions to subtract fees.
  • What if cooldown days are required? → Add extra states to DP.
💡 These follow-ups test your ability to adapt the DP solution to variations and optimize further.
🔍
Pattern Recognition

When to Use

1) Problem involves stock prices and transactions; 2) Limit on number of transactions; 3) Maximize profit; 4) Cannot hold multiple stocks simultaneously.

Signature Phrases

'at most two transactions''maximum profit you can achieve''you must sell before you buy again'

NOT This Pattern When

Problems with unlimited transactions or no transaction limits use different greedy or DP approaches.

Similar Problems

Best Time to Buy and Sell Stock I - single transactionBest Time to Buy and Sell Stock III - at most two transactionsBest Time to Buy and Sell Stock IV - at most k transactions

Practice

(1/5)
1. Given the following code for maxProfit with k=1 and prices = [1, 5, 3], what is the final returned value?
easy
A. 4
B. 5
C. 3
D. 0

Solution

  1. Step 1: Trace buy and sell arrays for each price

    Initialize buy=[-inf,-inf], sell=[0,0]. For price=1: buy[1]=max(-inf,0-1)=-1, sell[1]=0. For price=5: buy[1]=-1, sell[1]=max(0,-1+5)=4. For price=3: buy[1]=-1, sell[1]=max(4,-1+3)=4.
  2. Step 2: Return sell[k]

    sell[1]=4 is the max profit after 1 transaction.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Profit from buying at 1 and selling at 5 is 4 [OK]
Hint: Track buy/sell updates backward for correct profit [OK]
Common Mistakes:
  • Confusing buy and sell updates order
  • Off-by-one in transaction index
2. You are given a list of daily stock prices and want to maximize profit by buying and selling any number of times, but you cannot hold more than one stock at a time. Which algorithmic approach guarantees the optimal solution for this problem?
easy
A. Greedy approach summing all positive price differences between consecutive days
B. Dynamic Programming using a state machine to track holding and not holding states
C. Pure brute force recursion exploring all buy/sell combinations without memoization
D. Divide and conquer by splitting the price array and merging results

Solution

  1. Step 1: Understand problem constraints

    The problem allows unlimited transactions but only one stock can be held at a time.
  2. Step 2: Identify suitable algorithm

    For this problem, the greedy approach of summing all positive price differences between consecutive days guarantees the optimal profit. This works because any profitable transaction can be decomposed into these increments.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Greedy summing positive differences is optimal for unlimited transactions without cooldown or fees [OK]
Hint: Sum all positive price differences between consecutive days to get max profit [OK]
Common Mistakes:
  • Assuming DP is required when greedy suffices
  • Using brute force without memoization
  • Trying divide and conquer without state tracking
3. Given the following code for the Jump Game II problem, what is the value of the variable jumps returned when the input is [2,1,1]?
easy
A. 3
B. 2
C. 1
D. 0

Solution

  1. Step 1: Trace variables for input [2,1,1]

    n=3, loop i in [0,1]. - i=0: furthest = max(0, 0+2) = 2; i == current_end (0), so jumps = 1, current_end = 2 - i=1: furthest = max(2, 1+1) = 2; i != current_end (2), no jump increment Loop ends.
  2. Step 2: Return jumps

    jumps = 1 is returned, meaning minimum 1 jump to reach the end.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    One jump from index 0 to last index covers entire array [OK]
Hint: Increment jumps only when i reaches current_end [OK]
Common Mistakes:
  • Incrementing jumps too early or too late
  • Off-by-one errors in loop range
  • Misunderstanding when to update current_end
4. What is the time complexity of the space-optimized bottom-up DP solution for the Best Time -- K Transactions problem, given n days and k transactions?
medium
A. O(n * 2^k) due to exponential state combinations
B. O(k) because we only keep arrays of size k regardless of n
C. O(n * k) because for each day we update k states in reverse order
D. O(n^2) since for each day we scan all previous days

Solution

  1. Step 1: Identify loops in the code

    Outer loop runs over n days, inner loop runs over k transactions in reverse order.
  2. Step 2: Calculate complexity

    Each iteration does O(1) work, total O(n * k). No recursion stack or nested scanning over days.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Code has two nested loops: days and transactions [OK]
Hint: Two nested loops: days and transactions [OK]
Common Mistakes:
  • Assuming exponential due to recursion
  • Confusing with O(n^2) from naive solutions
5. What is the time complexity of the optimal dynamic programming solution for the House Robber II problem using the space-optimized approach shown below?
def rob_linear(nums, start, end):
    prev, curr = 0, 0
    for i in range(start, end + 1):
        prev, curr = curr, max(curr, prev + nums[i])
    return curr

def rob(nums):
    n = len(nums)
    if n == 1:
        return nums[0]
    return max(rob_linear(nums, 0, n - 2), rob_linear(nums, 1, n - 1))
medium
A. O(n) with constant extra space
B. O(n²) due to nested loops
C. O(2^n) because of recursion without memoization
D. O(n) because each house is processed twice in two linear scans

Solution

  1. Step 1: Identify loops in rob_linear

    rob_linear runs a single loop from start to end, which is O(n) in worst case.
  2. Step 2: Count calls in rob

    rob calls rob_linear twice, each over roughly n/2 elements, so total O(n) time.
  3. Step 3: Consider space usage

    The DP uses only constant extra space (variables prev and curr) during computation.
  4. Final Answer:

    Option A -> Option A
  5. Quick Check:

    Two linear scans over n elements with constant space -> O(n) time and O(1) space [OK]
Hint: Two linear scans sum to O(n) time with constant space [OK]
Common Mistakes:
  • Confusing recursion with DP
  • Assuming nested loops cause O(n²)
  • Ignoring that calls are sequential, not nested
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard