Bird
Raised Fist0
Interview Prepdp-linear-fibonaccihardAmazonGoogle

Best Time - K Transactions

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

Imagine you are a stock trader who can make at most K buy-sell transactions to maximize profit over a series of days. How do you decide when to buy and sell?

💡 This problem is a classic example of dynamic programming where beginners struggle because of the multiple states involved: days, transactions left, and holding status. Understanding the state transitions is key.
📋
Problem Statement

Given an integer array prices where prices[i] is the price of a given stock on the i-th day, and an integer k representing the maximum number of transactions allowed, find the maximum profit you can achieve. You may not engage in multiple transactions simultaneously (i.e., you must sell the stock before you buy again). Return the maximum profit achievable with at most k transactions.

1 ≤ prices.length ≤ 10^50 ≤ prices[i] ≤ 10^50 ≤ k ≤ 10^9
💡
Example
Input"k = 2, prices = [3,2,6,5,0,3]"
Output7

Buy on day 2 (price=2) and sell on day 3 (price=6), profit = 4. Then buy on day 5 (price=0) and sell on day 6 (price=3), profit = 3. Total profit = 7.

Input"k = 2, prices = [2,4,1]"
Output2

Buy on day 1 (price=2) and sell on day 2 (price=4), profit = 2. No second transaction needed.

  • k = 0 → output 0 (no transactions allowed)
  • prices array length = 1 → output 0 (only one day, no profit possible)
  • k >= n/2 → treat as unlimited transactions problem
  • prices all equal → output 0 (no profit possible)
🔁
Why DP?
Why greedy fails:

A greedy approach that buys low and sells high locally fails because it doesn't consider the limit on the number of transactions. For example, buying and selling every profitable pair might exceed k transactions, leading to invalid solutions.

DP state:

dp[i][j][0 or 1] represents the maximum profit on day i with at most j transactions completed, where 0 means not holding stock and 1 means holding stock.

Recurrence:dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1] + prices[i]) dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0] - prices[i])

On day i, if not holding stock, either we did nothing or sold today. If holding stock, either we did nothing or bought today (using one transaction).

⚠️
Common Mistakes
Not handling the case when k >= n/2

Solution times out or uses unnecessary DP for unlimited transactions

✅ Add a shortcut to sum all positive price differences when k is large

Mixing up holding and not holding states

Incorrect profit calculations and wrong answers

✅ Carefully track and update dp states for holding and not holding separately

Iterating forwards in space optimized DP

Overwrites dp values prematurely, causing incorrect results

✅ Iterate backwards over transactions to preserve previous states

Forgetting to reduce transactions count when buying

Overcounts transactions leading to invalid solutions

✅ Decrease transactions count only when selling (completing a transaction)

Using 3D DP without memoization or tabulation

Exponential time complexity and TLE

✅ Use memoization or bottom-up DP to avoid recomputation

🧠
Brute Force (Pure Recursion)
💡 This approach explores all possible buy/sell decisions on each day, which helps understand the problem's exponential nature and why optimization is necessary.

Intuition

Try every possible transaction sequence by deciding to buy, sell, or skip on each day, tracking transactions left and holding state.

Algorithm

  1. Define a recursive function with parameters: current day, transactions left, and holding state.
  2. If no days left or no transactions left, return 0 profit.
  3. If holding stock, decide to sell or skip.
  4. If not holding, decide to buy or skip.
  5. Return the maximum profit from these choices.
💡 The recursion tree grows exponentially because each day has multiple choices, making it inefficient but conceptually clear.
Recurrence:f(i, k, holding) = max( if holding: max(f(i+1, k, holding), f(i+1, k-1, not holding) + prices[i]), else: max(f(i+1, k, holding), f(i+1, k, holding) - prices[i]) )
</>
Code
def maxProfit(k, prices):
    n = len(prices)
    def dfs(i, transactions_left, holding):
        if i == n or transactions_left == 0:
            return 0
        if holding:
            sell = dfs(i + 1, transactions_left - 1, False) + prices[i]
            skip = dfs(i + 1, transactions_left, True)
            return max(sell, skip)
        else:
            buy = dfs(i + 1, transactions_left, True) - prices[i]
            skip = dfs(i + 1, transactions_left, False)
            return max(buy, skip)
    return dfs(0, k, False)

# Example usage
if __name__ == '__main__':
    print(maxProfit(2, [3,2,6,5,0,3]))  # Output: 7
Line Notes
def dfs(i, transactions_left, holding):Defines recursive function with day, transactions left, and holding state
if i == n or transactions_left == 0:Base case: no days or no transactions left means no profit
if holding:If currently holding stock, can sell or skip
sell = dfs(i + 1, transactions_left - 1, False) + prices[i]Sell stock today, gain price, reduce transactions
buy = dfs(i + 1, transactions_left, True) - prices[i]Buy stock today, spend price, holding becomes True
return dfs(0, k, False)Start recursion from day 0, with k transactions, not holding stock
public class Solution {
    public int maxProfit(int k, int[] prices) {
        return dfs(prices, 0, k, false);
    }
    private int dfs(int[] prices, int i, int transactionsLeft, boolean holding) {
        if (i == prices.length || transactionsLeft == 0) return 0;
        if (holding) {
            int sell = dfs(prices, i + 1, transactionsLeft - 1, false) + prices[i];
            int skip = dfs(prices, i + 1, transactionsLeft, true);
            return Math.max(sell, skip);
        } else {
            int buy = dfs(prices, i + 1, transactionsLeft, true) - prices[i];
            int skip = dfs(prices, i + 1, transactionsLeft, false);
            return Math.max(buy, skip);
        }
    }
    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.maxProfit(2, new int[]{3,2,6,5,0,3})); // 7
    }
}
Line Notes
public int maxProfit(int k, int[] prices)Entry point calling recursive dfs
if (i == prices.length || transactionsLeft == 0) return 0;Base case: no days or no transactions left
if (holding)If holding stock, decide to sell or skip
int sell = dfs(prices, i + 1, transactionsLeft - 1, false) + prices[i];Sell stock today, gain price, reduce transactions
int buy = dfs(prices, i + 1, transactionsLeft, true) - prices[i];Buy stock today, spend price, holding becomes true
System.out.println(sol.maxProfit(2, new int[]{3,2,6,5,0,3}));Test example prints expected output
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int dfs(const vector<int>& prices, int i, int transactionsLeft, bool holding) {
    if (i == prices.size() || transactionsLeft == 0) return 0;
    if (holding) {
        int sell = dfs(prices, i + 1, transactionsLeft - 1, false) + prices[i];
        int skip = dfs(prices, i + 1, transactionsLeft, true);
        return max(sell, skip);
    } else {
        int buy = dfs(prices, i + 1, transactionsLeft, true) - prices[i];
        int skip = dfs(prices, i + 1, transactionsLeft, false);
        return max(buy, skip);
    }
}

int maxProfit(int k, vector<int>& prices) {
    return dfs(prices, 0, k, false);
}

int main() {
    vector<int> prices = {3,2,6,5,0,3};
    cout << maxProfit(2, prices) << endl; // 7
    return 0;
}
Line Notes
int dfs(const vector<int>& prices, int i, int transactionsLeft, bool holding)Recursive function with day, transactions left, holding state
if (i == prices.size() || transactionsLeft == 0) return 0;Base case: no days or no transactions left
if (holding)If holding stock, choose to sell or skip
int sell = dfs(prices, i + 1, transactionsLeft - 1, false) + prices[i];Sell stock today, gain price, reduce transactions
int buy = dfs(prices, i + 1, transactionsLeft, true) - prices[i];Buy stock today, spend price, holding becomes true
cout << maxProfit(2, prices) << endl;Print result for example test
function maxProfit(k, prices) {
    const n = prices.length;
    function dfs(i, transactionsLeft, holding) {
        if (i === n || transactionsLeft === 0) return 0;
        if (holding) {
            const sell = dfs(i + 1, transactionsLeft - 1, false) + prices[i];
            const skip = dfs(i + 1, transactionsLeft, true);
            return Math.max(sell, skip);
        } else {
            const buy = dfs(i + 1, transactionsLeft, true) - prices[i];
            const skip = dfs(i + 1, transactionsLeft, false);
            return Math.max(buy, skip);
        }
    }
    return dfs(0, k, false);
}

console.log(maxProfit(2, [3,2,6,5,0,3])); // 7
Line Notes
function dfs(i, transactionsLeft, holding)Recursive helper with day, transactions left, holding
if (i === n || transactionsLeft === 0) return 0;Base case: no days or no transactions left
if (holding)If holding stock, decide to sell or skip
const sell = dfs(i + 1, transactionsLeft - 1, false) + prices[i];Sell stock today, gain price, reduce transactions
const buy = dfs(i + 1, transactionsLeft, true) - prices[i];Buy stock today, spend price, holding becomes true
console.log(maxProfit(2, [3,2,6,5,0,3]));Print example output
⏱
Complexity
TimeO(2^n)
SpaceO(n) recursion stack

Each day has two choices (buy/sell/skip), leading to exponential calls.

💡 For n=20, this means over a million calls, which is impractical.
Interview Verdict: TLE

This approach is too slow for large inputs but is essential to understand the problem structure.

🧠
Top-Down DP with Memoization
💡 Memoization caches results of recursive calls to avoid recomputation, drastically improving efficiency over brute force.

Intuition

Store results of subproblems identified by day, transactions left, and holding state to reuse them instead of recomputing.

Algorithm

  1. Create a 3D memo table indexed by day, transactions left, and holding state.
  2. Before recursive call, check if result is cached; if yes, return it.
  3. Otherwise, compute recursively as in brute force and store result.
  4. Return the cached or computed result.
💡 Memoization turns exponential recursion into polynomial time by avoiding repeated work.
Recurrence:Same as brute force but with memoization to store f(i,k,holding).
</>
Code
def maxProfit(k, prices):
    n = len(prices)
    from functools import lru_cache
    @lru_cache(None)
    def dfs(i, transactions_left, holding):
        if i == n or transactions_left == 0:
            return 0
        if holding:
            sell = dfs(i + 1, transactions_left - 1, False) + prices[i]
            skip = dfs(i + 1, transactions_left, True)
            return max(sell, skip)
        else:
            buy = dfs(i + 1, transactions_left, True) - prices[i]
            skip = dfs(i + 1, transactions_left, False)
            return max(buy, skip)
    return dfs(0, k, False)

if __name__ == '__main__':
    print(maxProfit(2, [3,2,6,5,0,3]))  # 7
Line Notes
@lru_cache(None)Caches results of dfs calls to avoid recomputation
if i == n or transactions_left == 0:Base case for recursion termination
return max(sell, skip)Choose best action when holding stock
return max(buy, skip)Choose best action when not holding stock
return dfs(0, k, False)Start recursion with no stock held
import java.util.*;
public class Solution {
    private Integer[][][] memo;
    public int maxProfit(int k, int[] prices) {
        int n = prices.length;
        memo = new Integer[n][k + 1][2];
        return dfs(prices, 0, k, 0);
    }
    private int dfs(int[] prices, int i, int transactionsLeft, int holding) {
        if (i == prices.length || transactionsLeft == 0) return 0;
        if (memo[i][transactionsLeft][holding] != null) return memo[i][transactionsLeft][holding];
        int res;
        if (holding == 1) {
            int sell = dfs(prices, i + 1, transactionsLeft - 1, 0) + prices[i];
            int skip = dfs(prices, i + 1, transactionsLeft, 1);
            res = Math.max(sell, skip);
        } else {
            int buy = dfs(prices, i + 1, transactionsLeft, 1) - prices[i];
            int skip = dfs(prices, i + 1, transactionsLeft, 0);
            res = Math.max(buy, skip);
        }
        memo[i][transactionsLeft][holding] = res;
        return res;
    }
    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.maxProfit(2, new int[]{3,2,6,5,0,3})); // 7
    }
}
Line Notes
memo = new Integer[n][k + 1][2];Initialize memo table for caching results
if (memo[i][transactionsLeft][holding] != null) return memo[i][transactionsLeft][holding];Return cached result if available
memo[i][transactionsLeft][holding] = res;Store computed result in memo
return dfs(prices, 0, k, 0);Start recursion with no stock held
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

class Solution {
    vector<vector<vector<int>>> memo;
    int dfs(const vector<int>& prices, int i, int transactionsLeft, int holding) {
        if (i == prices.size() || transactionsLeft == 0) return 0;
        if (memo[i][transactionsLeft][holding] != -1) return memo[i][transactionsLeft][holding];
        int res;
        if (holding == 1) {
            int sell = dfs(prices, i + 1, transactionsLeft - 1, 0) + prices[i];
            int skip = dfs(prices, i + 1, transactionsLeft, 1);
            res = max(sell, skip);
        } else {
            int buy = dfs(prices, i + 1, transactionsLeft, 1) - prices[i];
            int skip = dfs(prices, i + 1, transactionsLeft, 0);
            res = max(buy, skip);
        }
        return memo[i][transactionsLeft][holding] = res;
    }
public:
    int maxProfit(int k, vector<int>& prices) {
        int n = prices.size();
        memo.assign(n, vector<vector<int>>(k + 1, vector<int>(2, -1)));
        return dfs(prices, 0, k, 0);
    }
};

int main() {
    Solution sol;
    vector<int> prices = {3,2,6,5,0,3};
    cout << sol.maxProfit(2, prices) << endl; // 7
    return 0;
}
Line Notes
memo.assign(n, vector<vector<int>>(k + 1, vector<int>(2, -1)));Initialize memo with -1 to indicate uncomputed states
if (memo[i][transactionsLeft][holding] != -1) return memo[i][transactionsLeft][holding];Return cached result if computed
return memo[i][transactionsLeft][holding] = res;Store and return computed result
return dfs(prices, 0, k, 0);Start recursion with no stock held
function maxProfit(k, prices) {
    const n = prices.length;
    const memo = new Map();
    function dfs(i, transactionsLeft, holding) {
        if (i === n || transactionsLeft === 0) return 0;
        const key = i + ',' + transactionsLeft + ',' + holding;
        if (memo.has(key)) return memo.get(key);
        let res;
        if (holding) {
            const sell = dfs(i + 1, transactionsLeft - 1, false) + prices[i];
            const skip = dfs(i + 1, transactionsLeft, true);
            res = Math.max(sell, skip);
        } else {
            const buy = dfs(i + 1, transactionsLeft, true) - prices[i];
            const skip = dfs(i + 1, transactionsLeft, false);
            res = Math.max(buy, skip);
        }
        memo.set(key, res);
        return res;
    }
    return dfs(0, k, false);
}

console.log(maxProfit(2, [3,2,6,5,0,3])); // 7
Line Notes
const memo = new Map();Initialize map to cache results
const key = i + ',' + transactionsLeft + ',' + holding;Create unique key for memoization
if (memo.has(key)) return memo.get(key);Return cached result if available
memo.set(key, res);Store computed result in cache
return dfs(0, k, false);Start recursion with no stock held
⏱
Complexity
TimeO(n * k * 2)
SpaceO(n * k * 2) for memo and recursion stack

Each state (day, transactions left, holding) is computed once and cached.

💡 For n=1000 and k=10, this means about 20,000 computations, which is efficient.
Interview Verdict: Accepted

Memoization makes the solution efficient enough for large inputs.

🧠
Bottom-Up DP (Tabulation)
💡 This approach builds the solution iteratively from the base cases, which is often easier to debug and understand than recursion.

Intuition

Use a 3D dp array to store max profit for each day, transaction count, and holding state, filling it from day 0 to n-1.

Algorithm

  1. Initialize a 3D dp array with dimensions (n+1) x (k+1) x 2 to zero.
  2. Iterate days from 1 to n, transactions from 1 to k.
  3. Update dp[i][j][0] as max of not holding or selling today.
  4. Update dp[i][j][1] as max of holding or buying today.
  5. Return dp[n][k][0] as max profit after all days.
💡 Filling the dp table iteratively ensures all dependencies are computed before use.
Recurrence:dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1] + prices[i-1]) dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0] - prices[i-1])
</>
Code
def maxProfit(k, prices):
    n = len(prices)
    if k >= n // 2:
        profit = 0
        for i in range(1, n):
            profit += max(0, prices[i] - prices[i-1])
        return profit
    dp = [[[0]*2 for _ in range(k+1)] for _ in range(n+1)]
    for i in range(1, n+1):
        for j in range(1, k+1):
            dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1] + prices[i-1])
            dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0] - prices[i-1])
    return dp[n][k][0]

if __name__ == '__main__':
    print(maxProfit(2, [3,2,6,5,0,3]))  # 7
Line Notes
if k >= n // 2:Optimization: if k large, treat as unlimited transactions
profit += max(0, prices[i] - prices[i-1])Sum all positive price differences for unlimited transactions
dp = [[[0]*2 for _ in range(k+1)] for _ in range(n+1)]Initialize 3D dp array with zeros
dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1] + prices[i-1])Max profit not holding stock today
dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0] - prices[i-1])Max profit holding stock today
public class Solution {
    public int maxProfit(int k, int[] prices) {
        int n = prices.length;
        if (k >= n / 2) {
            int profit = 0;
            for (int i = 1; i < n; i++) {
                profit += Math.max(0, prices[i] - prices[i - 1]);
            }
            return profit;
        }
        int[][][] dp = new int[n + 1][k + 1][2];
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= k; j++) {
                dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i - 1]);
                dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i - 1]);
            }
        }
        return dp[n][k][0];
    }
    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.maxProfit(2, new int[]{3,2,6,5,0,3})); // 7
    }
}
Line Notes
if (k >= n / 2)Optimization for large k to unlimited transactions
profit += Math.max(0, prices[i] - prices[i - 1]);Sum positive price differences
int[][][] dp = new int[n + 1][k + 1][2];Initialize 3D dp array
dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i - 1]);Max profit not holding stock today
dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i - 1]);Max profit holding stock today
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        int n = prices.size();
        if (k >= n / 2) {
            int profit = 0;
            for (int i = 1; i < n; i++) {
                profit += max(0, prices[i] - prices[i - 1]);
            }
            return profit;
        }
        vector<vector<vector<int>>> dp(n + 1, vector<vector<int>>(k + 1, vector<int>(2, 0)));
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= k; j++) {
                dp[i][j][0] = max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i - 1]);
                dp[i][j][1] = max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i - 1]);
            }
        }
        return dp[n][k][0];
    }
};

int main() {
    Solution sol;
    vector<int> prices = {3,2,6,5,0,3};
    cout << sol.maxProfit(2, prices) << endl; // 7
    return 0;
}
Line Notes
if (k >= n / 2)Optimization for large k to unlimited transactions
profit += max(0, prices[i] - prices[i - 1]);Sum positive price differences
vector<vector<vector<int>>> dp(n + 1, vector<vector<int>>(k + 1, vector<int>(2, 0)));Initialize 3D dp array
dp[i][j][0] = max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i - 1]);Max profit not holding stock today
dp[i][j][1] = max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i - 1]);Max profit holding stock today
function maxProfit(k, prices) {
    const n = prices.length;
    if (k >= Math.floor(n / 2)) {
        let profit = 0;
        for (let i = 1; i < n; i++) {
            profit += Math.max(0, prices[i] - prices[i - 1]);
        }
        return profit;
    }
    const dp = Array.from({ length: n + 1 }, () => Array.from({ length: k + 1 }, () => [0, 0]));
    for (let i = 1; i <= n; i++) {
        for (let j = 1; j <= k; j++) {
            dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i - 1]);
            dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i - 1]);
        }
    }
    return dp[n][k][0];
}

console.log(maxProfit(2, [3,2,6,5,0,3])); // 7
Line Notes
if (k >= Math.floor(n / 2))Optimization for large k to unlimited transactions
profit += Math.max(0, prices[i] - prices[i - 1]);Sum positive price differences
const dp = Array.from({ length: n + 1 }, () => Array.from({ length: k + 1 }, () => [0, 0]));Initialize 3D dp array
dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i - 1]);Max profit not holding stock today
dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i - 1]);Max profit holding stock today
⏱
Complexity
TimeO(n * k)
SpaceO(n * k)

We fill a dp table of size n*k with constant time operations per cell.

💡 For n=1000 and k=10, about 10,000 operations, efficient for interviews.
Interview Verdict: Accepted

Bottom-up DP is a standard and efficient solution for this problem.

🧠
Space Optimized Bottom-Up DP
💡 Since dp[i] depends only on dp[i-1], we can reduce space from O(n*k) to O(k) by rolling arrays.

Intuition

Use two 1D arrays to track holding and not holding states for each transaction count, updating them day by day.

Algorithm

  1. If k is large, use unlimited transactions shortcut.
  2. Initialize two arrays buy and sell of size k+1, buy filled with -infinity, sell with 0.
  3. Iterate over each price, update buy[j] and sell[j] for j from k down to 1.
  4. buy[j] = max(buy[j], sell[j-1] - price), sell[j] = max(sell[j], buy[j] + price).
  5. Return sell[k] as the maximum profit.
💡 Iterating backwards over transactions ensures previous states are not overwritten before use.
Recurrence:buy[j] = max(buy[j], sell[j-1] - price) sell[j] = max(sell[j], buy[j] + price)
</>
Code
def maxProfit(k, prices):
    n = len(prices)
    if k >= n // 2:
        return sum(max(prices[i] - prices[i-1], 0) for i in range(1, n))
    buy = [-float('inf')] * (k + 1)
    sell = [0] * (k + 1)
    for price in prices:
        for j in range(k, 0, -1):
            buy[j] = max(buy[j], sell[j-1] - price)
            sell[j] = max(sell[j], buy[j] + price)
    return sell[k]

if __name__ == '__main__':
    print(maxProfit(2, [3,2,6,5,0,3]))  # 7
Line Notes
if k >= n // 2:Shortcut for unlimited transactions
buy = [-float('inf')] * (k + 1)Initialize buy array with very low values
sell = [0] * (k + 1)Initialize sell array with zeros
for j in range(k, 0, -1):Iterate backwards to avoid overwriting needed values
buy[j] = max(buy[j], sell[j-1] - price)Update buy state for j transactions
sell[j] = max(sell[j], buy[j] + price)Update sell state for j transactions
public class Solution {
    public int maxProfit(int k, int[] prices) {
        int n = prices.length;
        if (k >= n / 2) {
            int profit = 0;
            for (int i = 1; i < n; i++) {
                profit += Math.max(0, prices[i] - prices[i - 1]);
            }
            return profit;
        }
        int[] buy = new int[k + 1];
        int[] sell = new int[k + 1];
        Arrays.fill(buy, Integer.MIN_VALUE);
        for (int price : prices) {
            for (int j = k; j >= 1; j--) {
                buy[j] = Math.max(buy[j], sell[j - 1] - price);
                sell[j] = Math.max(sell[j], buy[j] + price);
            }
        }
        return sell[k];
    }
    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.maxProfit(2, new int[]{3,2,6,5,0,3})); // 7
    }
}
Line Notes
if (k >= n / 2)Shortcut for unlimited transactions
Arrays.fill(buy, Integer.MIN_VALUE);Initialize buy array with very low values
for (int j = k; j >= 1; j--)Iterate backwards to preserve previous states
buy[j] = Math.max(buy[j], sell[j - 1] - price);Update buy state for j transactions
sell[j] = Math.max(sell[j], buy[j] + price);Update sell state for j transactions
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        int n = prices.size();
        if (k >= n / 2) {
            int profit = 0;
            for (int i = 1; i < n; i++) {
                profit += max(0, prices[i] - prices[i - 1]);
            }
            return profit;
        }
        vector<int> buy(k + 1, INT_MIN);
        vector<int> sell(k + 1, 0);
        for (int price : prices) {
            for (int j = k; j >= 1; j--) {
                buy[j] = max(buy[j], sell[j - 1] - price);
                sell[j] = max(sell[j], buy[j] + price);
            }
        }
        return sell[k];
    }
};

int main() {
    Solution sol;
    vector<int> prices = {3,2,6,5,0,3};
    cout << sol.maxProfit(2, prices) << endl; // 7
    return 0;
}
Line Notes
if (k >= n / 2)Shortcut for unlimited transactions
vector<int> buy(k + 1, INT_MIN);Initialize buy array with very low values
vector<int> sell(k + 1, 0);Initialize sell array with zeros
for (int j = k; j >= 1; j--)Iterate backwards to preserve previous states
buy[j] = max(buy[j], sell[j - 1] - price);Update buy state for j transactions
sell[j] = max(sell[j], buy[j] + price);Update sell state for j transactions
function maxProfit(k, prices) {
    const n = prices.length;
    if (k >= Math.floor(n / 2)) {
        let profit = 0;
        for (let i = 1; i < n; i++) {
            profit += Math.max(0, prices[i] - prices[i - 1]);
        }
        return profit;
    }
    const buy = new Array(k + 1).fill(-Infinity);
    const sell = new Array(k + 1).fill(0);
    for (const price of prices) {
        for (let j = k; j >= 1; j--) {
            buy[j] = Math.max(buy[j], sell[j - 1] - price);
            sell[j] = Math.max(sell[j], buy[j] + price);
        }
    }
    return sell[k];
}

console.log(maxProfit(2, [3,2,6,5,0,3])); // 7
Line Notes
if (k >= Math.floor(n / 2))Shortcut for unlimited transactions
const buy = new Array(k + 1).fill(-Infinity);Initialize buy array with very low values
const sell = new Array(k + 1).fill(0);Initialize sell array with zeros
for (let j = k; j >= 1; j--)Iterate backwards to preserve previous states
buy[j] = Math.max(buy[j], sell[j - 1] - price);Update buy state for j transactions
sell[j] = Math.max(sell[j], buy[j] + price);Update sell state for j transactions
⏱
Complexity
TimeO(n * k)
SpaceO(k)

Only two arrays of size k+1 are maintained and updated for each day.

💡 For n=1000 and k=10, only about 10,000 operations and small memory usage.
Interview Verdict: Accepted

This is the most efficient and memory-friendly approach for large inputs.

📊
All Approaches - One-Glance Tradeoffs
💡 In interviews, code bottom-up DP or space optimized DP for best balance of clarity and efficiency.
ApproachTimeSpaceStack RiskReconstructUse In Interview
1. Brute ForceO(2^n)O(n) recursion stackYes (deep recursion)YesMention only - never code
2. Top-Down DP with MemoizationO(n * k)O(n * k)Possible for large nYesGood for explaining optimization over brute force
3. Bottom-Up DP (Tabulation)O(n * k)O(n * k)NoYesStandard approach to code in interviews
4. Space Optimized Bottom-Up DPO(n * k)O(k)NoNo (without extra bookkeeping)Best for large inputs and memory constraints
💼
Interview Strategy
💡 Use this guide to understand the problem deeply, practice coding all approaches, and prepare to explain tradeoffs clearly in interviews.

How to Present

Clarify problem constraints and ask about large k optimization.Explain brute force recursion to show understanding of problem states.Introduce memoization to optimize overlapping subproblems.Present bottom-up DP for iterative clarity and efficiency.Finally, discuss space optimization for memory efficiency.

Time Allocation

Clarify: 2min → Approach: 3min → Code: 10min → Test & Optimize: 5min. Total ~20min

What the Interviewer Tests

Understanding of DP states and transitions, ability to optimize naive solutions, and knowledge of space-time tradeoffs.

Common Follow-ups

  • What if k is very large? → Use unlimited transactions shortcut.
  • Can you optimize space? → Use 1D arrays with backward iteration.
💡 These follow-ups test your ability to handle edge cases and optimize your solution further.
🔍
Pattern Recognition

When to Use

1) Problem involves stock prices and transactions 2) Limit on number of transactions k 3) Maximize profit with buy/sell constraints 4) Requires DP with states for day, transactions, holding

Signature Phrases

'at most k transactions''buy and sell stock''maximize profit with transaction limit'

NOT This Pattern When

Problems without transaction limits or with different constraints like single transaction only

Similar Problems

Best Time to Buy and Sell Stock II - unlimited transactionsBest Time to Buy and Sell Stock with Cooldown - cooldown constraintBest Time to Buy and Sell Stock with Transaction Fee - fee per transaction

Practice

(1/5)
1. Consider the following Python code for climbing stairs:
def climb_stairs(n):
    if n <= 1:
        return 1
    first, second = 1, 1
    for _ in range(2, n + 1):
        current = first + second
        first, second = second, current
    return second

print(climb_stairs(4))
What is the value of the variable current during the last iteration of the loop?
easy
A. 4
B. 2
C. 3
D. 5

Solution

  1. Step 1: Trace variable values for n=4

    Initialize first=1, second=1. Iterations: - i=2: current=1+1=2; first=1, second=2 - i=3: current=1+2=3; first=2, second=3 - i=4: current=2+3=5; first=3, second=5
  2. Step 2: Identify last iteration's current value

    The last iteration is i=4, current=5.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Last current computed is 5 for n=4 [OK]
Hint: Trace loop iterations carefully to find current value [OK]
Common Mistakes:
  • Confusing loop bounds and off-by-one errors
  • Returning previous current instead of last
2. What is the time complexity of the space-optimized DP solution for the Best Time -- 2 Transactions problem with input size n and fixed k=2?
medium
A. O(n^2)
B. O(n * k)
C. O(n)
D. O(k^n)

Solution

  1. Step 1: Identify loops in the code

    Outer loop runs n-1 times, inner loop runs k times (k=2 fixed). So total iterations ~ n*k.
  2. Step 2: Simplify complexity with fixed k

    Since k=2 is constant, O(n*k) simplifies to O(n).
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Linear time due to fixed number of transactions [OK]
Hint: Fixed k means linear time, not quadratic [OK]
Common Mistakes:
  • Confusing k as variable leading to O(n*k)
  • Assuming nested loops always mean O(n^2)
3. Examine the following code snippet for climbing stairs:
def climb_stairs(n):
    if n < 1:
        return 1
    first, second = 1, 1
    for i in range(2, n + 1):
        current = first + second
        first, second = second, current
    return second
Which line contains a subtle bug that can cause incorrect results or runtime errors?
medium
A. Line 2: Incorrect base case condition should be 'if n <= 1'
B. Line 4: Loop range should start from 1 instead of 2
C. Line 5: The addition 'first + second' should be 'first - second'
D. Line 6: The tuple assignment order is reversed

Solution

  1. Step 1: Analyze base case condition

    The condition 'if n < 1' returns 1 only when n is 0 or negative, but for n=1 it skips and runs the loop, which is incorrect.
  2. Step 2: Correct base case to 'if n <= 1'

    This ensures for n=0 or n=1, the function returns 1 immediately, matching problem definition.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Base case off-by-one causes wrong results for n=1 [OK]
Hint: Check base cases carefully for off-by-one errors [OK]
Common Mistakes:
  • Using '<' instead of '<=' in base cases
  • Incorrect loop start index
4. Consider the following buggy code for Jump Game II. Which line contains the subtle bug that causes incorrect results for input arrays of length 1?
medium
A. Line 2: n = len(nums)
B. Line 9: return jumps
C. Line 3: jumps = 0
D. Line 5: for i in range(n - 1):

Solution

  1. Step 1: Analyze loop range for single-element array

    When n=1, range(n-1) = range(0), so the loop does not run, and jumps remains 0.
  2. Step 2: Check if jumps=0 is correct for single-element input

    For an array of length 1, minimum jumps should be 0 (already at last index). The code returns 0, which is correct. However, if the problem expects at least one jump or code elsewhere assumes the loop runs, this can cause issues. The subtle bug is that the loop does not run for n=1, so no updates happen. If code elsewhere expects jumps increment, this is a problem.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Loop skipping for n=1 causes edge case bugs [OK]
Hint: Loop range excludes last index, skip for single-element arrays [OK]
Common Mistakes:
  • Not handling single-element arrays properly
  • Assuming loop always runs at least once
  • Returning jumps without base case check
5. If you are allowed to complete as many transactions as you like (buy and sell multiple times), which modification to the algorithm below correctly computes the maximum profit?
hard
A. Sum up all positive differences between consecutive days' prices to accumulate profit from every profitable transaction.
B. Keep the same code but multiply the final max_profit by the number of transactions allowed.
C. Use the same min_price tracking but reset min_price to current price after each profitable sale.
D. Use nested loops to check all pairs of buy and sell days and pick the maximum sum of profits from multiple transactions.

Solution

  1. Step 1: Understand multiple transactions scenario

    When unlimited transactions are allowed, profit is sum of all positive price differences between consecutive days.
  2. Step 2: Identify correct algorithm modification

    Summing positive differences directly captures all profitable trades without complex state tracking.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Summing positive daily gains yields max profit with unlimited transactions [OK]
Hint: Sum all positive consecutive price differences [OK]
Common Mistakes:
  • Multiplying single transaction profit by transaction count
  • Resetting min_price incorrectly
  • Using nested loops causing inefficiency
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