Facebook Pixel

2291. Maximum Profit From Trading Stocks 🔒

Problem Description

You are given two arrays of stock prices: present and future. The present[i] represents the current price of the i-th stock, and future[i] represents what that same stock will be worth one year from now. Both arrays have the same length and are 0-indexed.

You have a fixed budget to invest in stocks. Your goal is to maximize your profit by strategically buying stocks now and selling them in the future. The constraints are:

  • Each stock can be purchased at most once
  • You cannot spend more than your budget
  • The profit from a stock is calculated as future[i] - present[i]

For example, if a stock costs 10now(present[i]=10)andwillbeworth10 now (`present[i] = 10`) and will be worth 15 in the future (future[i] = 15), buying it would cost you 10fromyourbudgetandgiveyouaprofitof10 from your budget and give you a profit of 5.

The problem asks you to find the maximum total profit you can achieve by selecting which stocks to buy within your budget constraints. This is essentially a variant of the 0/1 knapsack problem where:

  • The "weight" of each item is the current price (present[i])
  • The "value" of each item is the profit (future[i] - present[i])
  • The "capacity" is your budget

Return the maximum profit you can make by optimally selecting which stocks to purchase.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When looking at this problem, we need to make decisions about which stocks to buy to maximize our profit. For each stock, we have a binary choice: either buy it or skip it. This immediately suggests a decision-making problem where we need to explore different combinations.

The key insight is recognizing this as a resource allocation problem. We have limited resources (our budget) and want to maximize our return (total profit). This pattern is classic dynamic programming territory, specifically the 0/1 knapsack problem.

Think about it this way: if we're standing at stock i with some remaining budget j, we need to decide whether buying this stock will lead to better overall profit. To make this decision optimally, we need to know: "What's the best profit I could have made with the previous stocks given different budget amounts?"

This naturally leads us to define a state f[i][j] representing the maximum profit achievable when:

  • We've considered the first i stocks
  • We have a budget of j to work with

For each stock at position i, we face two scenarios:

  1. Skip the stock: If we don't buy it, our profit remains the same as what we could achieve with the first i-1 stocks and the same budget j. So f[i][j] = f[i-1][j].

  2. Buy the stock: If we buy it, we need to:

    • Check if we can afford it (j >= present[i])
    • Check if it's profitable (future[i] > present[i])
    • Calculate the new profit: previous best with reduced budget plus this stock's profit
    • This gives us f[i][j] = f[i-1][j - present[i]] + (future[i] - present[i])

We take the maximum of these two choices. By building up our solution from smaller subproblems (fewer stocks, smaller budgets) to larger ones, we ensure we find the optimal combination of stocks to purchase within our budget constraint.

Learn more about Dynamic Programming patterns.

Solution Approach

We implement the dynamic programming solution using a 2D array to store our states. Let's walk through the implementation step by step:

1. Initialize the DP Table:

f = [[0] * (budget + 1) for _ in range(len(present) + 1)]

We create a 2D array f with dimensions (n+1) × (budget+1) where n is the number of stocks. The extra row and column handle the base case where we have 0 stocks or 0 budget, which naturally have 0 profit.

2. Iterate Through Each Stock:

for i, w in enumerate(present, 1):

We iterate through each stock, where i represents the stock index (starting from 1 for our DP table) and w represents the current price of the stock (present[i-1]).

3. Consider All Budget Amounts:

for j in range(budget + 1):

For each stock, we consider all possible budget amounts from 0 to our maximum budget.

4. Apply the DP Transition:

f[i][j] = f[i - 1][j]  # Case 1: Don't buy the stock

First, we set the default value to be the profit without buying the current stock.

if j >= w and future[i - 1] > w:
    f[i][j] = max(f[i][j], f[i - 1][j - w] + future[i - 1] - w)

Then, if we have enough budget (j >= w) and the stock is profitable (future[i-1] > w), we check if buying it would give us better profit:

  • f[i - 1][j - w]: The best profit from previous stocks with reduced budget
  • future[i - 1] - w: The profit from this stock (sell price - buy price)

We take the maximum between not buying and buying the stock.

5. Return the Result:

return f[-1][-1]

The answer is stored in f[n][budget], which represents the maximum profit when considering all stocks with the full budget.

Time Complexity: O(n × budget) where n is the number of stocks
Space Complexity: O(n × budget) for the DP table

Note: The space complexity could be optimized to O(budget) by using a 1D array since we only need the previous row to compute the current row, but the 2D approach is more intuitive to understand.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example to see how the dynamic programming solution works.

Example:

  • present = [5, 10, 3] (current stock prices)
  • future = [8, 12, 7] (future stock prices)
  • budget = 8

Profits for each stock:

  • Stock 0: profit = 8 - 5 = 3
  • Stock 1: profit = 12 - 10 = 2
  • Stock 2: profit = 7 - 3 = 4

Step 1: Initialize DP table We create a 4×9 table (3 stocks + 1) × (budget 8 + 1):

    Budget: 0  1  2  3  4  5  6  7  8
Stock 0:    0  0  0  0  0  0  0  0  0
Stock 1:    0  0  0  0  0  0  0  0  0
Stock 2:    0  0  0  0  0  0  0  0  0
Stock 3:    0  0  0  0  0  0  0  0  0

Step 2: Process Stock 0 (price=5, profit=3) For each budget j from 0 to 8:

  • j < 5: Can't afford it, so f[1][j] = f[0][j] = 0
  • j ≥ 5: Can buy it! f[1][j] = max(0, f[0][j-5] + 3) = 3
    Budget: 0  1  2  3  4  5  6  7  8
Stock 0:    0  0  0  0  0  0  0  0  0
Stock 1:    0  0  0  0  0  3  3  3  3
Stock 2:    0  0  0  0  0  0  0  0  0
Stock 3:    0  0  0  0  0  0  0  0  0

Step 3: Process Stock 1 (price=10, profit=2) For each budget j:

  • j < 10: Can't afford it, so f[2][j] = f[1][j]
  • Since our max budget is 8, we can never buy stock 1!
    Budget: 0  1  2  3  4  5  6  7  8
Stock 0:    0  0  0  0  0  0  0  0  0
Stock 1:    0  0  0  0  0  3  3  3  3
Stock 2:    0  0  0  0  0  3  3  3  3
Stock 3:    0  0  0  0  0  0  0  0  0

Step 4: Process Stock 2 (price=3, profit=4) For each budget j:

  • j < 3: Can't afford it, f[3][j] = f[2][j]
  • j = 3: f[3][3] = max(f[2][3], f[2][0] + 4) = max(0, 0+4) = 4
  • j = 4: f[3][4] = max(f[2][4], f[2][1] + 4) = max(0, 0+4) = 4
  • j = 5: f[3][5] = max(f[2][5], f[2][2] + 4) = max(3, 0+4) = 4
  • j = 6: f[3][6] = max(f[2][6], f[2][3] + 4) = max(3, 0+4) = 4
  • j = 7: f[3][7] = max(f[2][7], f[2][4] + 4) = max(3, 0+4) = 4
  • j = 8: f[3][8] = max(f[2][8], f[2][5] + 4) = max(3, 3+4) = 7
    Budget: 0  1  2  3  4  5  6  7  8
Stock 0:    0  0  0  0  0  0  0  0  0
Stock 1:    0  0  0  0  0  3  3  3  3
Stock 2:    0  0  0  0  0  3  3  3  3
Stock 3:    0  0  0  4  4  4  4  4  7

Result: The maximum profit is f[3][8] = 7

This means we should buy Stock 0 (cost 5, profit 3) and Stock 2 (cost 3, profit 4) for a total cost of 8 and total profit of 7.

The key insight: at each step, we're asking "Given this budget and these stocks available, should I buy the current stock or not?" The DP table helps us make optimal decisions by storing the best results for all subproblems.

Solution Implementation

1class Solution:
2    def maximumProfit(self, present: List[int], future: List[int], budget: int) -> int:
3        """
4        Dynamic programming solution for maximizing profit from stock investments.
5      
6        Args:
7            present: List of current prices for each stock
8            future: List of future prices for each stock
9            budget: Total available budget for investments
10          
11        Returns:
12            Maximum profit achievable within the budget
13        """
14        n = len(present)
15      
16        # Initialize DP table: dp[i][j] represents max profit using first i stocks with budget j
17        dp = [[0] * (budget + 1) for _ in range(n + 1)]
18      
19        # Fill the DP table
20        for i in range(1, n + 1):
21            current_price = present[i - 1]
22            future_price = future[i - 1]
23          
24            for current_budget in range(budget + 1):
25                # Option 1: Don't buy the current stock
26                dp[i][current_budget] = dp[i - 1][current_budget]
27              
28                # Option 2: Buy the current stock if profitable and within budget
29                if current_budget >= current_price and future_price > current_price:
30                    profit_from_current = future_price - current_price
31                    dp[i][current_budget] = max(
32                        dp[i][current_budget],
33                        dp[i - 1][current_budget - current_price] + profit_from_current
34                    )
35      
36        # Return the maximum profit achievable with full budget
37        return dp[n][budget]
38
1class Solution {
2    /**
3     * Calculates maximum profit from buying and selling items with a budget constraint.
4     * Uses dynamic programming (0/1 knapsack problem variant).
5     * 
6     * @param present Array of current prices for each item
7     * @param future Array of future prices for each item
8     * @param budget Maximum amount available to spend
9     * @return Maximum profit that can be achieved
10     */
11    public int maximumProfit(int[] present, int[] future, int budget) {
12        int numItems = present.length;
13      
14        // dp[i][j] represents maximum profit using first i items with budget j
15        int[][] dp = new int[numItems + 1][budget + 1];
16      
17        // Fill the DP table
18        for (int itemIndex = 1; itemIndex <= numItems; itemIndex++) {
19            for (int currentBudget = 0; currentBudget <= budget; currentBudget++) {
20                // Option 1: Skip current item (inherit profit from previous item)
21                dp[itemIndex][currentBudget] = dp[itemIndex - 1][currentBudget];
22              
23                // Option 2: Buy current item if budget allows
24                int currentItemCost = present[itemIndex - 1];
25                if (currentBudget >= currentItemCost) {
26                    int profitFromCurrentItem = future[itemIndex - 1] - present[itemIndex - 1];
27                    int totalProfitWithCurrentItem = dp[itemIndex - 1][currentBudget - currentItemCost] + profitFromCurrentItem;
28                  
29                    // Choose maximum between skipping and buying current item
30                    dp[itemIndex][currentBudget] = Math.max(
31                        dp[itemIndex][currentBudget], 
32                        totalProfitWithCurrentItem
33                    );
34                }
35            }
36        }
37      
38        // Return maximum profit using all items with full budget
39        return dp[numItems][budget];
40    }
41}
42
1class Solution {
2public:
3    int maximumProfit(vector<int>& present, vector<int>& future, int budget) {
4        int n = present.size();
5      
6        // dp[i][j] represents the maximum profit using first i stocks with budget j
7        // dp[i][j] = maximum profit considering stocks 0 to i-1 with budget j
8        int dp[n + 1][budget + 1];
9        memset(dp, 0, sizeof(dp));
10      
11        // Iterate through each stock
12        for (int i = 1; i <= n; ++i) {
13            // Iterate through each possible budget amount
14            for (int j = 0; j <= budget; ++j) {
15                // Option 1: Don't buy the current stock (i-1 indexed)
16                // Inherit the maximum profit from previous stocks
17                dp[i][j] = dp[i - 1][j];
18              
19                // Option 2: Buy the current stock if budget allows
20                // Check if we have enough budget to buy current stock at present price
21                if (j >= present[i - 1]) {
22                    // Calculate profit if we buy this stock:
23                    // Previous state's profit + (selling price - buying price)
24                    int profitWithCurrentStock = dp[i - 1][j - present[i - 1]] + future[i - 1] - present[i - 1];
25                  
26                    // Take the maximum between not buying and buying current stock
27                    dp[i][j] = max(dp[i][j], profitWithCurrentStock);
28                }
29            }
30        }
31      
32        // Return the maximum profit using all n stocks with given budget
33        return dp[n][budget];
34    }
35};
36
1/**
2 * Calculates the maximum profit that can be achieved with a given budget
3 * by buying items at present prices and selling them at future prices.
4 * This is a 0/1 knapsack problem where we maximize profit within budget constraints.
5 * 
6 * @param present - Array of present prices for each item
7 * @param future - Array of future prices for each item
8 * @param budget - Total budget available for investment
9 * @returns Maximum profit achievable
10 */
11function maximumProfit(present: number[], future: number[], budget: number): number {
12    // Initialize DP array where dp[j] represents max profit with budget j
13    const dp: number[] = new Array(budget + 1).fill(0);
14  
15    // Iterate through each item
16    for (let itemIndex = 0; itemIndex < present.length; ++itemIndex) {
17        const currentPrice: number = present[itemIndex];
18        const futurePrice: number = future[itemIndex];
19      
20        // Traverse budget from high to low to avoid using same item multiple times
21        // This ensures each item is considered only once (0/1 knapsack)
22        for (let currentBudget = budget; currentBudget >= currentPrice; --currentBudget) {
23            // Calculate profit for this item: futurePrice - currentPrice
24            const profitFromItem: number = futurePrice - currentPrice;
25          
26            // Update max profit: either skip this item or buy it
27            dp[currentBudget] = Math.max(
28                dp[currentBudget],                                    // Skip current item
29                dp[currentBudget - currentPrice] + profitFromItem     // Buy current item
30            );
31        }
32    }
33  
34    // Return the maximum profit achievable with full budget
35    return dp[budget];
36}
37

Time and Space Complexity

The time complexity of this code is O(n × budget), where n is the length of the present array (which equals the length of the future array). This is because we have two nested loops: the outer loop iterates through all items (from index 1 to n), and the inner loop iterates through all possible budget values (from 0 to budget). Each iteration performs constant time operations, resulting in n × (budget + 1) total operations.

The space complexity is O(n × budget) as well. This is due to the 2D dynamic programming table f that we create with dimensions (len(present) + 1) × (budget + 1), which equals (n + 1) × (budget + 1). This table stores all intermediate results for the dynamic programming solution, where f[i][j] represents the maximum profit achievable using the first i items with a budget of j.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Including Unprofitable Stocks in the Knapsack

One common pitfall is forgetting to check whether a stock is actually profitable before considering it for purchase. The code correctly handles this with the condition future_price > current_price, but developers often miss this check and include stocks with negative or zero profit.

Incorrect approach:

# Missing profit check - might include stocks that lose money
if current_budget >= current_price:
    profit_from_current = future_price - current_price  # Could be negative!
    dp[i][current_budget] = max(
        dp[i][current_budget],
        dp[i - 1][current_budget - current_price] + profit_from_current
    )

Correct approach:

# Only consider stocks that will make a profit
if current_budget >= current_price and future_price > current_price:
    profit_from_current = future_price - current_price
    dp[i][current_budget] = max(
        dp[i][current_budget],
        dp[i - 1][current_budget - current_price] + profit_from_current
    )

2. Off-by-One Errors in Array Indexing

When using a DP table with dimensions (n+1) × (budget+1), it's easy to confuse the indices. The DP table uses 1-based indexing for stocks while the input arrays use 0-based indexing.

Incorrect approach:

# Wrong indexing - using i directly for present/future arrays
for i in range(1, n + 1):
    current_price = present[i]  # IndexError!
    future_price = future[i]    # IndexError!

Correct approach:

# Proper indexing - subtract 1 when accessing the input arrays
for i in range(1, n + 1):
    current_price = present[i - 1]
    future_price = future[i - 1]

3. Space Optimization Pitfall

While optimizing space from O(n × budget) to O(budget) using a 1D array, a common mistake is updating the array from left to right, which causes values to be overwritten before they're used.

Incorrect space-optimized approach:

# Wrong: iterating forward overwrites values we still need
dp = [0] * (budget + 1)
for i in range(n):
    for j in range(budget + 1):  # Forward iteration - WRONG!
        if j >= present[i] and future[i] > present[i]:
            dp[j] = max(dp[j], dp[j - present[i]] + future[i] - present[i])

Correct space-optimized approach:

# Correct: iterate backwards to preserve previous values
dp = [0] * (budget + 1)
for i in range(n):
    for j in range(budget, present[i] - 1, -1):  # Backward iteration
        if future[i] > present[i]:
            dp[j] = max(dp[j], dp[j - present[i]] + future[i] - present[i])

The backward iteration ensures that when we compute dp[j], the value dp[j - present[i]] still contains the result from the previous iteration (without the current stock), not the updated value from the current iteration.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

How many times is a tree node visited in a depth first search?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More