2291. Maximum Profit From Trading Stocks
Problem Description
The problem is essentially a variation of the classic 0/1 Knapsack problem. You are given two arrays present
and future
of the same length, which represent the current and future prices of stocks. With a given budget
, your task is to determine the maximum profit that can be gained by buying stocks at present prices and selling them in the future considering that you can't spend more than the budget. You can buy each stock at most once. The maximum profit is calculated by finding the difference between the future price and the present price for the stocks you decide to buy and summing these differences up. The challenge is to select the stocks in such a way that the total cost does not exceed the budget while maximizing the profit.
Intuition
The intuition behind the solution is to use dynamic programming (DP) to solve the problem in a bottom-up manner. We define a DP array f
with a length of budget + 1
, where each index j
represents the maximum profit that can be achieved with a budget j
. We initialize the DP array with zero values because initially, with a budget of 0, we cannot achieve any profit.
We iterate over each stock, and for each stock, we iterate backward through the DP array starting from the current budget down to the price of the current stock. This reverse iteration is necessary to ensure that each stock is considered at most once. For each budget j
, we update the DP value at j
to be the maximum of its current value and the value at j - present_stock_price
plus the profit that could be made by buying the current stock (future_stock_price - present_stock_price
).
This way, f[j]
represents the best decision we can make considering the first i
stocks for a given budget j
. When we reach f[budget]
, it contains the maximum profit we can make given our total budget.
The reverse iteration of the budget within the loop makes sure that we do not reuse the same stock multiple times as it simulates buying stocks exactly once by ensuring the current stock's budget isn't used previously in the same iteration.
The problem is solved when we fill the DP array, at which point f[-1]
(which is the same as f[budget]
) contains the solution, i.e., the maximum possible profit.
Learn more about Dynamic Programming patterns.
Solution Approach
The solution uses dynamic programming as its core algorithmic approach. The idea is to build up a solution step-by-step for incremental values of the budget, ensuring that at each step, the maximum profit that can be obtained without exceeding that budget is accounted for.
The key data structure used here is a list f
with a length of budget + 1
. This list is initialized with zeros, as initially, no profit can be made without spending money (budget = 0
). The list f
is used to store the maximum profit possible for every budget from 0
to budget
.
Let's delve deeper into the algorithm:
-
We iterate over each stock with a loop
for a, b in zip(present, future):
. The variablesa
andb
represent the present and future price of each stock, respectively. -
Within each iteration over the stocks, we iterate over the budgets from high to low
for j in range(budget, a - 1, -1):
. This is the reverse iteration that ensures we only consider buying a stock once. -
At each budget
j
, we make a decision: Can buying this stock at its current pricea
increase our profit when selling it in the future for priceb
? To make this decision, we check iff[j - a] + b - a
(which is our previous best profit at a reduced budgetj - a
, plus the profit from buying and selling this stock) is greater than the current best profit atf[j]
. -
If it is beneficial to buy the stock, we update
f[j]
with the new maximum profitmax(f[j], f[j - a] + b - a)
. Heref[j - a] + b - a
represents the profit made by buying this stock (which isfuture_price - present_price
) plus the profit that we could have made with the remaining budget after buying the stock (f[j - a]
). -
Finally, after considering all stocks for all possible budgets, we return
f[-1]
. Due to zero-indexing in Python,f[-1]
refers to the last element of the listf
, which corresponds to the maximum profit that can be achieved with the full budget.
The algorithm efficiently determines the maximum profit that can be achieved under the constraint of the budget by examining each possibility only once. This is made possible due to the nature of dynamic programming, which breaks down the problem into simpler subproblems and reuses their solutions.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's use a simple example to illustrate the solution approach. Suppose you have the following input:
present
: [1, 2, 3]future
: [2, 3, 5]budget
: 5
Using these inputs, we want to find the maximum profit we can make by buying stocks at present prices and selling them at future prices without exceeding our budget.
Initialization:
- We initialize our DP list
f
of lengthbudget + 1
, which is5 + 1 = 6
, with zeros, sof
becomes[0, 0, 0, 0, 0, 0]
.
Iteration over stocks:
-
First stock:
present
is 1 andfuture
is 2.- We check budgets starting from 5 down to 1 (the price of the stock).
- At
j = 5
, we consider buying this stock by comparingf[5 - 1] + (2 - 1)
withf[5]
. Sincef[4] + 1
is 1 andf[5]
is 0, we updatef[5]
to 1. - We repeat this for budget
j = 4, 3, 2, 1
. The listf
now becomes[0, 1, 1, 1, 1, 1]
.
-
Second stock:
present
is 2 andfuture
is 3.- This time we start from the budget of 5 and go down to 2.
- At
j = 5
, we check if buying this stock is better than any previous decision by checkingf[5 - 2] + (3 - 2)
againstf[5]
. We calculatef[3] + 1
is 2 andf[5]
is 1, so we updatef[5]
to 2. - We do similar updates for
j = 4
andj = 3
. Now,f
is[0, 1, 1, 2, 2, 2]
.
-
Third stock:
present
is 3 andfuture
is 5.- We iterate from budget 5 to 3.
- At
j = 5
, we comparef[5 - 3] + (5 - 3)
withf[5]
. Thusf[2] + 2
is 3 andf[5]
is 2, we updatef[5]
to 3. - At
j = 4
,f[4]
remains 2 since we can't buy this stock with a budget of 1. - So
f
becomes[0, 1, 1, 2, 2, 3]
.
Returning the result:
- The last element in list
f
is 3, so we returnf[-1]
which is the maximum profit possible with the initial budget, which is 3.
This walkthrough shows that by using dynamic programming, we can efficiently calculate the maximum profit without exceeding our budget by making smart decisions at each step.
Solution Implementation
1class Solution:
2 def maximumProfit(self, present_values: List[int], future_values: List[int], budget: int) -> int:
3 # Create a table to store the maximum profit that can be achieved with each budget
4 profit_table = [0] * (budget + 1)
5
6 # Iterate over each item pair of present and future values
7 for present_value, future_value in zip(present_values, future_values):
8 # Update the profit table in reverse to avoid using updated values in the same iteration
9 for current_budget in range(budget, present_value - 1, -1):
10 # Calculate the profit by buying an item at present value and selling at future value
11 profit = future_value - present_value
12
13 # Update the profit table with the maximum profit so far
14 # Either keep the current profit for the budget or
15 # Take the profit by investing present_value and adding to the profit found with the remaining budget
16 profit_table[current_budget] = max(profit_table[current_budget],
17 profit_table[current_budget - present_value] + profit)
18
19 # The last element of the profit table holds the maximum profit achievable with the given budget
20 return profit_table[-1]
21
1class Solution {
2 public int maximumProfit(int[] presentValues, int[] futureValues, int budget) {
3 int numberOfItems = presentValues.length; // Total number of items
4
5 // dp array to store the maximum profit for each budget upto the given budget
6 int[] dp = new int[budget + 1];
7
8 // Iterate over each item
9 for (int i = 0; i < numberOfItems; ++i) {
10 int presentValue = presentValues[i];
11 int futureValue = futureValues[i];
12
13 // Iterate over all possible remaining budgets in reverse
14 // to avoid using same item's profit more than once
15 for (int currentBudget = budget; currentBudget >= presentValue; --currentBudget) {
16 // Update dp array with the maximum profit achievable with the current budget
17 dp[currentBudget] = Math.max(dp[currentBudget], dp[currentBudget - presentValue] + futureValue - presentValue);
18 }
19 }
20
21 // Return the maximum profit that can be achieved with the given budget
22 return dp[budget];
23 }
24}
25
1#include <vector>
2#include <cstring>
3#include <algorithm>
4using namespace std;
5
6class Solution {
7public:
8 // This function calculates the maximum profit given the present and future prices of gifts
9 // within a specified budget constraint.
10 int maximumProfit(vector<int>& presentPrices, vector<int>& futurePrices, int budget) {
11 int n = presentPrices.size(); // Number of gifts
12 vector<int> dp(budget + 1, 0); // Initialize DP array to store max profits with 0 as default
13
14 // Iterate over all the gifts
15 for (int i = 0; i < n; ++i) {
16 int buyPrice = presentPrices[i]; // Buy price of the current gift
17 int sellPrice = futurePrices[i]; // Future (sell) price of the current gift
18
19 // Traverse the budget from back to front to avoid using updated states
20 for (int j = budget; j >= buyPrice; --j) {
21 // Update the dp array for the current budget 'j' with the greater value between
22 // the previous dp value for 'j' and the dp value for 'j - buyPrice'
23 // added with the profit of selling the current gift (sellPrice - buyPrice).
24 dp[j] = max(dp[j], dp[j - buyPrice] + sellPrice - buyPrice);
25 }
26 }
27 return dp[budget]; // Return the maximum profit for the given budget
28 }
29};
30
1function maximumProfit(currentPrices: number[], futurePrices: number[], budget: number): number {
2 // Initialize a dynamic programming array of length `budget + 1` with zeros.
3 const dp = new Array<number>(budget + 1).fill(0);
4
5 // Iterate over each item (both present and future prices).
6 for (let i = 0; i < currentPrices.length; ++i) {
7 // Capture the current and future price of the item.
8 const currentPrice = currentPrices[i];
9 const futurePrice = futurePrices[i];
10
11 // Iterate over the budget from high to low to avoid recomputation.
12 for (let currentBudget = budget; currentBudget >= currentPrice; --currentBudget) {
13 // Calculate the maximum profit by comparing whether to buy this item or not.
14 dp[currentBudget] = Math.max(
15 dp[currentBudget], // Not buying the item
16 dp[currentBudget - currentPrice] + futurePrice - currentPrice // Buying the item
17 );
18 }
19 }
20
21 // The last element in the dynamic programming array contains the maximum profit.
22 return dp[budget];
23}
24
Time and Space Complexity
The given Python function maximumProfit
aims to determine the maximum profit that can be made given a budget constraint and lists of present and future values of items.
Time Complexity
The time complexity of the function is determined by the nested loops present in the code. The outer loop runs once for each pair of present and future values, while the inner loop runs for each value of the budget from the current item's present value to 1, decrementing on each iteration.
Given that n
is the total number of items (length of the present
or future
list), and budget
is the maximum budget available, the outer loop runs n
times, and the inner loop runs at most budget
times for each iteration of the outer loop.
Therefore, the overall time complexity is O(n * budget)
, where n
is the length of the lists.
Space Complexity
The space complexity is determined by the storage used by the algorithm outside of the input storage. In this case, a list f
of size budget + 1
is created to store temporary values computed during the execution of the function.
This results in a space complexity of O(budget)
, since the list f
grows linearly with respect to the budget
parameter.
Learn more about how to find time and space complexity quickly using problem constraints.
What's the output of running the following function using the following tree as input?
1def serialize(root):
2 res = []
3 def dfs(root):
4 if not root:
5 res.append('x')
6 return
7 res.append(root.val)
8 dfs(root.left)
9 dfs(root.right)
10 dfs(root)
11 return ' '.join(res)
12
1import java.util.StringJoiner;
2
3public static String serialize(Node root) {
4 StringJoiner res = new StringJoiner(" ");
5 serializeDFS(root, res);
6 return res.toString();
7}
8
9private static void serializeDFS(Node root, StringJoiner result) {
10 if (root == null) {
11 result.add("x");
12 return;
13 }
14 result.add(Integer.toString(root.val));
15 serializeDFS(root.left, result);
16 serializeDFS(root.right, result);
17}
18
1function serialize(root) {
2 let res = [];
3 serialize_dfs(root, res);
4 return res.join(" ");
5}
6
7function serialize_dfs(root, res) {
8 if (!root) {
9 res.push("x");
10 return;
11 }
12 res.push(root.val);
13 serialize_dfs(root.left, res);
14 serialize_dfs(root.right, res);
15}
16
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
LeetCode Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way we
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!