Summary of General Solutions for Stock Trading Problems

The stock trading problems in Leetcode are as follows:

121. Best Time to Buy and Sell Stock
122. Best Time to Buy and Sell Stock II
123. Best Time to Buy and Sell Stock III
188. Best Time to Buy and Sell Stock IV
309. Best Time to Buy and Sell Stock with Cooldown
714.Best Time to Buy and Sell Stock with Transaction Fee
 
I -- Common Situation
The idea of ​​this article comes from the following question: Given an array representing daily stock prices, what determines the maximum profit we can get?
Most people quickly get into things like "that depends on what day we're in and how many transactions we're allowed to do".
Of course, these are important factors because they are shown in the problem description. However, there is a less obvious but crucial factor in judging maximum profit, which will be explained below.
Let us first determine the notation to simplify our analysis, let prices be an array of stock prices of length n, i mark the ith day (i will go from 0 to n-1), and k mark the maximum number of trades allowed to be completed,
T[i][k] is the maximum profit that can be obtained after at most k transactions at the end of the ith day. Obviously we have the base case: T[-1][k]=T[i][0]=0, that is no stock or no trade resulting in no profit
(marking day 1 as i=0 so i=0 means with no stock). Now if we can take T[i][k] and its subproblems T[i-1][k], T[i][k-1], T[i-1][k-1], . .., we will have a working recurrence relation
Makes the problem solvable recursively, so how do we implement it?
The most common direct way is to look at the actions on day i, how many options do we have? The answer is three: buy, sell, rest. Which should we take?
The answer is: we don't really know, but we can find which is the easiest. Assuming no other constraints, we can try each option and choose the one that gives us the most profit. But we do have an additional constraint, which
is that we cannot make repeated transactions at the same time, which means that if we decide to buy on the ith day, we should not hold the stock; if we decide to sell on the ith day, we should There should be exactly one stock. The number of shares in our hands
is the factor mentioned above that affects our day i behavior and also affects the maximum profit.
Therefore our definition of T[i][k] should be split into two parts: T[i][k][0] and T[i][k][1], the former marking at most k transactions and actions Then the maximum profit at the end of day i with no stock in our hand, the
latter marking the maximum profit at the end of day i with only one stock in our hand after at most k trades and actions, now the base case and The recurrence relation can be written as:

1. Base case:

T[-1][k][0]=0, T[-1][k][1]=-Infinity
T[i][0][0] =0, T[i][0][1]=-Infinity

2. Recursion:

T[i][k][0]=max(T[i-1][k][0],T[ i-1][k][1]+prices[i])
T[i][k][1]=max(T[i-1][k][1],T[i-1][k -1][0]-prices[i])

For the base case, T[-1][k][0] = T[i][0][0] = 0 has the same meaning as before when T[-1][k][1] = T[i ][0][1] = -Infinity underscores the fact that
it is impossible for us to have a stock in hand if there is no stock available or allowed to trade.
For T[i][k][0] in the recursion, the only actions that can be taken on day i are rest and sell, since we have no stock in hand at the end of the day. T[i-1][k][0] is the maximum profit when the action break is taken,
and T[i-1][k][1] + prices[i] is the maximum profit when the action sell is taken profit. If the maximum allowed number of trades remains the same, since a trade consists of two paired actions - buy and sell. Only behavioral buying will change
the maximum number of transactions allowed. (There is an alternative interpretation here)
For T[i][k][1] in the recursion, the only actions that can be taken on day i are rest and buy, since we only have one in hand at the end of the day stock. T[i-1][k][1] is the maximum profit when taking action to rest,
and T[i-1][k-1][0] - prices[i] is the maximum profit when taking action to buy profit. Note that the maximum number of transactions allowed is reduced by one, because the purchase on the i-th day will use up one transaction.
To find the maximum profit at the end of the last day, we can simply loop through the array prices and update T[i][k][0] and T[i][k][1] via the above recurrence relation. The final answer is T[i][k][0] (we always
have a bigger profit if we end up with 0 stocks in hand)
 
II -- Application to special cases
The six stock problems mentioned earlier can be Classified by the value of k, where k is the maximum number of transactions allowed (the last two also have additional requirements such as cooling periods or transaction fees). I will apply the general solution to these problems one by one.

Case I: k=1
For this case, we really have two unknown variables in each day: T[i][1][0] and T[i][1][1], the recurrence relation says:

T[i][1][0] = max(T[i-1][1][0], T[i-1][1][1] + prices[i])
T[i][1][1] = max(T[i-1][1][1], T[i-1][0][0] - prices[i]) = max(T[i-1][1][1], -prices[i])

In the second equation we take full advantage of the base case T[i][0][0]=0.
It is easy to directly write the solution in O(n) time complexity and O(n) space complexity, based on the above two equations. However, if you notice that the maximum profit on day i is actually only dependent on those profits on day i-1,
then the space complexity drops to O(1). This is the spatially optimal solution:

public int maxProfit(int[] prices){
int T_i10=0,T_i11=Integer.MIN_VALUE;
for(int price:prices){
T_i10=Math.max(T_i10,T_i11 + price);
T_i11=Math.max(T_i11, -price);
}
return T_i10;
}
 
Case II: k=+Infinity
If k is positive infinity, then there is no real difference between k and k-1, implying T[i-1][k-1][ 0] = T[i-1][k][0] and T[i-1][k-1][1] = T[i-1][k][1], so we still have Two unknown variables: T[i][k][0] and T[i][k][1], k=+Infinity, the recurrence relation is as follows:

T[i][k][0] = max(T[i-1][k][0], T[i-1][k][1] + prices[i])
T[i][k][1] = max(T[i-1][k][1], T[i-1][k-1][0] - prices[i]) = max(T[i-1][k][1], T[i-1][k][0] - prices[i])

In the second equation we will take advantage of T[i-1][k-1][0] = T[i-1][k][0], O(n) time complexity and O(1) The solution to the space complexity is as follows:

public int maxProfit(int[] prices) {
int T_ik0 = 0, T_ik1 = Integer.MIN_VALUE;

for (int price : prices) {
int T_ik0_old = T_ik0;
T_ik0 = Math.max(T_ik0, T_ik1 + price);
T_ik1 = Math .max(T_ik1, T_ik0_old - price);
}

return T_ik0;
}
 
(note: the old value of cached T_ik0, which is the variable T_ik0_old, is not possible, special thanks to 0x0101 and elvina for clarifying this)
 
This solution proposes a greedy Strategies to maximize profits: Whenever possible, buy stocks at each minimum and quickly sell at the next local maximum. This is equivalent to finding increasing subsequences in prices, buying at the beginning of each subsequence, and selling at the end of each subsequence. It's easy to show that this is equivalent to accumulating profits whenever it's profitable, as mentioned in https://discuss.leetcode.com/topic/726/is-this-question-a-joke.
 
Case III: k=2
is similar to the k=1 case, except now we have four variables per day instead of two: T[i][1][0], T[i][1][1], T [i][2][0], T[i][2][1], the recurrence relation is as follows:

T[i][2][0] = max(T[i-1][2][ 0], T[i-1][2][1] + prices[i])
T[i][2][1] = max(T[i-1][2][1], T[i-1][1][0] - prices[i])
T[i][1 ][0] = max(T[i-1][1][0], T[i-1][1][1] + prices[i])
T[i][1][1] = max (T[i-1][1][1], -prices[i])
 
In the last equation we take full advantage of the base case T[i][0][0]=0, the following solution has O(n ) has space complexity and O(1)

public int maxProfit(int[] prices) {
int T_i10 = 0, T_i11 = Integer.MIN_VALUE, T_i20 = 0, T_i21 = Integer.MIN_VALUE;

for (int price : prices) {
T_i20 = Math.max(T_i20, T_i21 + price );
T_i21 = Math.max(T_i21, T_i10 - price);
T_i10 = Math.max(T_i10, T_i11 + price);
T_i11 = Math.max(T_i11, -price);
}

return T_i20;
}

  Case IV: k is Arbitrary
  This is the most general case so at each day we need to update all the maximal profits with different values ​​of k corresponding to 0 or 1 stock in our hand at the end of each day.
  However, there is a smiling optimization that we can do if k exceeds some critical value, beyond which the maximum profit is no longer dependent on the number of trades allowed but is limited by the number of shares available (the length of the prices array), let We figure out what the critical value will be.
  A profitable trade takes at least two days (one day to buy another day to sell, the buy price is not less than the sell price), if the length of the prices array is n, the maximum number of profitable trades is n/2, after which there is no possibility ,
which implies that the maximum profit will remain the same.
  Therefore, the critical value of k is n/2. If the given value of k is not less than this value, in other words, k>=n/2, we can extend k to positive infinity, and the problem is equivalent to Case II.
  The following is a solution with time complexity O(kn) and space complexity O(k), without optimization, the code can satisfy the time constraints for large K values.

public int maxProfit(int k, int[] prices) {
if (k >= prices.length >>> 1) {
int T_ik0 = 0, T_ik1 = Integer.MIN_VALUE;

for (int price : prices) {
int T_ik0_old = T_ik0 ;
T_ik0 = Math.max(T_ik0, T_ik1 + price);
T_ik1 = Math.max(T_ik1, T_ik0_old - price);
}

return T_ik0;
}

int[] T_ik0 = new int[k + 1];
int[] T_ik1 = new int[k + 1];
Arrays.fill(T_ik1, Integer.MIN_VALUE);

for (int price : prices) {
for (int j = k; j > 0; j--) {
T_ik0[j] = Math. max(T_ik0[j], T_ik1[j] + price);
T_ik1[j] = Math.max(T_ik1[j], T_ik0[j - 1] - price);
}
}

return T_ik0[k];
}

The solution is similar to the one in the https://discuss.leetcode.com/topic/8984/a-concise-dp-solution-in-java post, where I use a reverse loop of the T array to avoid temporary variables. It has also been shown that it is possible to do forward loops without using temporary variables.

    Case V: k=+Infinity with Cooldown
  This example is more similar to Case II in that they have the same value of k, except that the recursion requires a simple modification to satisfy the cooldown requirement. The original recurrence given in Case II is:

T[i][k][0] = max(T[i-1][k][0], T[i-1][k][1] + prices[i])
T[i][k][1] = max(T[i-1][k][1], T[i-1][k][0] - prices[i])

  But because of the cooling-off period, we cannot buy the stock on day i if the stock is sold on day i-1, so, in the second equation above, we should actually use T[i-2][k ][0] instead of T[i-1][k][0]
if we intend to buy on day i. Everything else remains the same and the new recursion is:

T[i][k][0] = max(T[i-1][k][0], T[i-1][k][1] + prices[i])
T[i][k ][1] = max(T[i-1][k][1], T[i-2][k][0] - prices[i])
 
The following is O(n) time complexity and O( 1) Solution of space complexity:

public int maxProfit(int[] prices) {
int T_ik0_pre = 0, T_ik0 = 0, T_ik1 = Integer.MIN_VALUE;

for (int price : prices) {
int T_ik0_old = T_ik0;
T_ik0 = Math.max(T_ik0, T_ik1 + price) ;
T_ik1 = Math.max(T_ik1, T_ik0_pre - price);
T_ik0_pre = T_ik0_old;
}

return T_ik0;
}
dietpepsi shared a very good solution which proved to be the same as above https://discuss.leetcode.com/topic /30421/share-my-thinking-process
    case VI:k=+Infinity but with transaction fees
    Again this example is equivalent to case II because they have the same value of k, except that the current recurrence relation needs to be slightly modified to satisfy Transaction fee requirements. Case II gives the original recurrence relation as follows:

T[i][k][0] = max(T[i-1][k][0], T[i-1][k][1] + prices[i])
T[i][k][1] = max(T[i-1][k][1], T[i-1][k][0] - prices[i])
 
Since we now need to pay for each trade, this value should be subtracted from the profit of buying and selling stocks on day i, so the new recurrence relation should be any of the following:


T[i][k][0] = max(T[i-1][k][0], T[i-1][k][1] + prices[i])
T[i][k][1] = max(T[i-1][k][1], T[i-1][k][0] - prices[i] - fee)
 


T[i][k][0] = max(T[i-1][k][0], T[i-1][k][1] + prices[i] - fee)
T[i] [k][1] = max(T[i-1][k][1], T[i-1][k][0] - prices[i])
 
Note that when we subtract the cost we have two option, because each trade is characterized by two actions that occur in pairs -- buy and sell
fees can be paid when we buy a stock (the first set of equations) or when we sell a stock (the second set of equations), The following are solutions in O(n) time complexity and O(1) space complexity corresponding to these two options, the second solution we need to pay attention to possible overflow.
 
Solution I - Pay a transaction fee when buying a stock

public int maxProfit(int[] prices, int fee) {
int T_ik0 = 0, T_ik1 = Integer.MIN_VALUE;

for (int price : prices) {
int T_ik0_old = T_ik0;
T_ik0 = Math.max(T_ik0, T_ik1 + price);
T_ik1 = Math.max(T_ik1, T_ik0_old - price - fee);
}

return T_ik0;
}
 
Solution II - Pay transaction fee when selling stock

public int maxProfit(int[] prices, int fee) {
long T_ik0 = 0, T_ik1 = Integer.MIN_VALUE;

for (int price : prices) {
long T_ik0_old = T_ik0;
T_ik0 = Math.max(T_ik0, T_ik1 + price - fee );
T_ik1 = Math.max(T_ik1, T_ik0_old - price);
}

return (int)T_ik0;
}
 
III -- Summary
  In conclusion, the most general solution to the stock problem can be characterized by three factors, i-day number, maximum allowable The number of trades k and the number of shares we hold at the end of each day. I have shown the recursion of maximum profits and their termination cases, O(nk) time complexity and O(k) space complexity. The results were applied sequentially to six examples, the latter two being slightly modified due to additional requirements. I should mention that https://discuss.leetcode.com/user/peterleetcode also describes a good solution that is general for any value of k. If you are interested, take a look.
   Hope this helps you, and happy coding!

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324893888&siteId=291194637