LeetCode 121 - Best Time to Buy and Sell Stock
LeetCode - Best Time to Buy and Sell Stock II
LeetCode - Best Time to Buy and Sell Stock III
LeetCode 188 - Best Time to Buy and Sell Stock IV
Leetcode 309 - Best Time to Buy and Sell Stock with Cooldown
LeetCode 714 - Best Time to Buy and Sell Stock with Transaction Fee
https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/description/
Buying at prices[0] = 1
Selling at prices[3] = 8
Buying at prices[4] = 4
Selling at prices[5] = 9
The total profit is ((8 - 1) - 2) + ((9 - 4) - 2) = 8.
http://www.cnblogs.com/grandyang/p/7776979.html
LeetCode - Best Time to Buy and Sell Stock II
LeetCode - Best Time to Buy and Sell Stock III
LeetCode 188 - Best Time to Buy and Sell Stock IV
Leetcode 309 - Best Time to Buy and Sell Stock with Cooldown
LeetCode 714 - Best Time to Buy and Sell Stock with Transaction Fee
Your are given an array of integers
prices
, for which the i
-th element is the price of a given stock on day i
; and a non-negative integer fee
representing a transaction fee.
You may complete as many transactions as you like, but you need to pay the transaction fee for each transaction. You may not buy more than 1 share of a stock at a time (ie. you must sell the stock share before you buy again.)
Return the maximum profit you can make.
Example 1:
Input: prices = [1, 3, 2, 8, 4, 9], fee = 2 Output: 8 Explanation: The maximum profit can be achieved by:
Note:
0 < prices.length <= 50000
.0 < prices[i] < 50000
.0 <= fee < 50000
.
又是一道股票交易的题,之前已经有过类似的五道题了,fun4LeetCode大神的帖子做了amazing的归纳总结,有时间的话博主也写个总结。这道题跟Best Time to Buy and Sell Stock II其实最像,但是由于那道题没有交易费的限制,所以我们就无脑贪婪就可以了,见到利润就往上加。但是这道题有了交易费,所以当卖出的利润小于交易费的时候,我们就不应该卖了,不然亏了。所以这道题还是还是得用动态规划来做,按照fun4LeetCode大神的理论,本质其实是个三维dp数组,由于第三维只有两种情况,卖出和保留,而且第二维交易的次数在这道题中没有限制,所以我们用两个一维数组就可以了,sold[i]表示第i天卖掉股票此时的最大利润,hold[i]表示第i天保留手里的股票此时的最大利润。那么我们来分析递推公式,在第i天,如果我们要卖掉手中的股票,那么此时我们的总利润应该是前一天手里有股票的利润(不然没股票卖毛啊),加上此时的卖出价格,减去交易费得到的利润总值,跟前一天卖出的利润相比,取其中较大值,如果前一天卖出的利润较大,那么我们就前一天卖了,不留到今天了。然后来看如果第i天不卖的利润,就是昨天股票卖了的利润然后今天再买入股票,得减去今天的价格,得到的值和昨天股票保留时的利润相比,取其中的较大值,如果昨天保留股票的利润大,那么我们就继续保留到今天,所以递推时可以得到:
sold[i] = max(sold[i - 1], hold[i - 1] + prices[i] - fee);
hold[i] = max(hold[i - 1], sold[i - 1] - prices[i]);
https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/discuss/108873/faster-than-the-%22max%22-solution-and-very-clear-answer
https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/discuss/108870/Most-consistent-ways-of-dealing-with-the-series-of-stock-problems
Given any
day I
, its max profit status boils down to one of the two status below:
(1) buy status:
(2) sell status:
buy[i]
represents the max profit at day i
in buy status, given that the last action you took is a buy action at day K
, where K<=i
. And you have the right to sell at day i+1
, or do nothing.(2) sell status:
sell[i]
represents the max profit at day i
in sell status, given that the last action you took is a sell action at day K
, where K<=i
. And you have the right to buy at day i+1
, or do nothing.
Let's walk through from base case.
Base case:
We can start from buy status, which means we buy stock at
Or we can start from sell status, which means we sell stock at
Given that we don't have any stock at hand in day 0, we set sell status to be 0.
We can start from buy status, which means we buy stock at
day 0
.buy[0]=-prices[0]
;Or we can start from sell status, which means we sell stock at
day 0
.Given that we don't have any stock at hand in day 0, we set sell status to be 0.
sell[0]=0
;
Status transformation:
At
Or
At
At
day i
, we may buy stock (from previous sell status) or do nothing (from previous buy status):buy[i] = Math.max(buy[i - 1], sell[i - 1] - prices[i]);
Or
At
day i
, we may sell stock (from previous buy status) or keep holding (from previous sell status):sell[i] = Math.max(sell[i - 1], buy[i - 1] + prices[i]);
Finally:
We will return
We will return
sell[last_day]
as our result, which represents the max profit at the last day, given that you took sell action at any day before the last day.
We can apply transaction fee at either buy status or sell status.
So here come our two solutions:
Solution I -- pay the fee when buying the stock:
public int maxProfit(int[] prices, int fee) {
if (prices.length <= 1) return 0;
int days = prices.length, buy[] = new int[days], sell[] = new int[days];
buy[0]=-prices[0]-fee;
for (int i = 1; i<days; i++) {
buy[i] = Math.max(buy[i - 1], sell[i - 1] - prices[i] - fee); // keep the same as day i-1, or buy from sell status at day i-1
sell[i] = Math.max(sell[i - 1], buy[i - 1] + prices[i]); // keep the same as day i-1, or sell from buy status at day i-1
}
return sell[days - 1];
}
Solution II -- pay the fee when selling the stock:
public int maxProfit(int[] prices, int fee) {
if (prices.length <= 1) return 0;
int days = prices.length, buy[] = new int[days], sell[] = new int[days];
buy[0]=-prices[0];
for (int i = 1; i<days; i++) {
buy[i] = Math.max(buy[i - 1], sell[i - 1] - prices[i]); // keep the same as day i-1, or buy from sell status at day i-1
sell[i] = Math.max(sell[i - 1], buy[i - 1] + prices[i] - fee); // keep the same as day i-1, or sell from buy status at day i-1
}
return sell[days - 1];
}
https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/discuss/108867/C++-concise-solution-O(n)-time-O(1)-space
X. O(1) space
https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/discuss/108892/Java-DP-solution-O(n)-to-O(1)-space
X. https://leetcode.com/articles/best-time-to-buy-and-sell-stock-with-transaction-fee/
public int maxProfit(int[] prices, int fee) { if (prices==null || prices.length==0) return 0; int pre_buy = -prices[0]-fee, pre_sell = 0; int curr_buy = 0, curr_sell = 0; for (int i=1; i<prices.length; i++){ curr_buy = Math.max(pre_buy, pre_sell-prices[i]-fee); curr_sell = Math.max(pre_sell, pre_buy+prices[i]); pre_buy = curr_buy; pre_sell = curr_sell; } return curr_sell; }
The solution maintains two states:
s0 = profit having no stock
s1 = profit having 1 stock
The code iterates through the stock prices, and updates s0, s1 respectively. The run time is O(n).
update s0 by selling the stock from s1, so s0 = max(s0, s1+p);
update s1 by buying the stock from s0, so s1 = max(s1, s0-p-fee);
https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/discuss/108868/Java-simple-DP-solutions.-O(n)
This problem is just like the other stock problems.
At given day, we can do 1 out of 4 things:
At given day, we can do 1 out of 4 things:
- buy stock
- hold stock
- do nothing with empty portfolio
- sell stock
We have 4 arrays with the length of # of the days, recording the max profit at given day if we do given operation.
public int maxProfit(int[] prices, int fee) {
if(prices.length <= 1) return 0;
int[] buy = new int[prices.length];
int[] hold = new int[prices.length];
int[] skip = new int[prices.length];
int[] sell = new int[prices.length];
// the moment we buy a stock, our balance should decrease
buy[0] = 0 - prices[0];
// assume if we have stock in the first day, we are still in deficit
hold[0] = 0 - prices[0];
for(int i = 1; i < prices.length; i++){
// We can only buy on today if we sold stock
// or skipped with empty portfolio yesterday
buy[i] = Math.max(skip[i-1], sell[i-1]) - prices[i];
// Can only hold if we bought or already holding stock yesterday
hold[i] = Math.max(buy[i-1], hold[i-1]);
// Can skip only if we skipped, or sold stock yesterday
skip[i] = Math.max(skip[i-1], sell[i-1]);
// Can sell only if we bought, or held stock yesterday
sell[i] = Math.max(buy[i-1], hold[i-1]) + prices[i] - fee;
}
// Get the max of all the 4 actions on the last day.
int max = Math.max(buy[prices.length - 1], hold[prices.length - 1]);
max = Math.max(skip[prices.length - 1], max);
max = Math.max(sell[prices.length - 1], max);
return Math.max(max, 0);
}
我们发现不管是卖出还是保留,第i天到利润只跟第i-1天有关系,所以我们可以优化空间,用两个变量来表示当前的卖出和保留的利润,更新方法和上面的基本相同,就是开始要保存sold的值,不然sold先更新后,再更新hold时就没能使用更新前的值了
int maxProfit(vector<int>& prices, int fee) { int sold = 0, hold = -prices[0]; for (int price : prices) { int t = sold; sold = max(sold, hold + price - fee); hold = max(hold, t - price); } return sold; }
hold[i] : The maximum profit of holding stock until day i;
notHold[i] : The maximum profit of not hold stock until day i;
notHold[i] : The maximum profit of not hold stock until day i;
dp transition function:
For day i, we have two situations:
For day i, we have two situations:
- Hold stock:
(1) We do nothing on day i: hold[i - 1];
(2) We buy stock on day i: notHold[i - 1] - prices[i]; - Not hold stock:
(1) We do nothing on day i: notHold[i - 1];
(2) We sell stock on day i: hold[i - 1] + prices[i] - fee;
From the dp transition function, we can see the i th state are only based on the i-1 th state. So we could optimize space to O(1) using two variables.
public int maxProfit(int[] prices, int fee) {
if (prices == null || prices.length <= 1) {
return 0;
}
int len = prices.length;
int hold = -prices[0];
int notHold = 0;
for (int i = 1; i < prices.length; i++) {
hold = Math.max(hold, notHold - prices[i]);
notHold = Math.max(notHold, hold + prices[i] - fee);
}
return notHold;
}
X. https://leetcode.com/articles/best-time-to-buy-and-sell-stock-with-transaction-fee/
At the end of the
i
-th day, we maintain cash
, the maximum profit we could have if we did not have a share of stock, and hold
, the maximum profit we could have if we owned a share of stock.
To transition from the
i
-th day to the i+1
-th day, we either sell our stock cash = max(cash, hold + prices[i] - fee)
or buy a stock hold = max(hold, cash - prices[i])
. At the end, we want to return cash
. We can transform cash
first without using temporary variables because selling and buying on the same day can't be better than just continuing to hold the stock.public int maxProfit(int[] prices, int fee) { int cash = 0, hold = -prices[0]; for (int i = 1; i < prices.length; i++) { cash = Math.max(cash, hold + prices[i] - fee); hold = Math.max(hold, cash - prices[i]); } return cash; }
public int maxProfit(int[] prices, int fee) { if (prices==null || prices.length==0) return 0; int pre_buy = -prices[0]-fee, pre_sell = 0; int curr_buy = 0, curr_sell = 0; for (int i=1; i<prices.length; i++){ curr_buy = Math.max(pre_buy, pre_sell-prices[i]-fee); curr_sell = Math.max(pre_sell, pre_buy+prices[i]); pre_buy = curr_buy; pre_sell = curr_sell; } return curr_sell; }
X.Greedy
http://www.programmersought.com/article/168957256/
int maxProfit(vector<int>& prices, int fee) {
int len=prices.size();
if(len==0 || len==1)
return 0;
int sum=0;
int peak=0;
int least=INT_MAX;
for(auto ele : prices){
if(peak-ele>fee){
sum+=(peak-least-fee);
least=ele;
peak=0;
}
least=min(ele,least);
if(ele-least>fee){
peak=max(peak,ele);
}
}
if(peak-least>fee)
sum+=(peak-least-fee);
return sum;
}
贪心选择的关键是找到一个最大后是不是能够卖掉stock,重新开始寻找买入机会。比如序列1 3 2 8,如果发现2小于3就完成交易买1卖3,此时由于fee=2,(3-1-fee)+(8-2-fee)<(8-1-fee),所以说明卖早了,令max是当前最大price,当(max-price[i]>=fee)时可以在max处卖出,且不会存在卖早的情况,再从i开始重新寻找买入机会
curProfit记录了当前一次交易能得到的最大收益,只有当maxP-prices[i]>=fee时,才将curProfit累加到总的收益中。最后一次交易不需要考虑是否早卖了,所以直接累加最后一次的curProfit。
int maxProfit(vector<int>& prices, int fee) {
int profit=0;
int curProfit=0;
int minP=prices[0];
int maxP=prices[0];
int i;
for(i=1;i<prices.size();i++){
minP=min(minP,prices[i]);
maxP=max(maxP,prices[i]);
curProfit=max(curProfit,prices[i]-minP-fee);
if((maxP-prices[i])>=fee){//can just sell the stock at maxP day.
profit+=curProfit;
curProfit=0;
minP=prices[i];
maxP=prices[i];
}
}
return profit+curProfit;//the last trade have to be made if there is some profit
}
https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/discuss/108884/JavaC++-Clean-Code-(DPGreedy)
buy records the price we buy, and res records the result.
The trick is to buy and sell everytime we can gain a profit and twists the buy variable to the sell value minus the fee.
The trick is to buy and sell everytime we can gain a profit and twists the buy variable to the sell value minus the fee.
def maxProfit(self, prices, fee):
buy = prices[0]
res = 0
for p in prices:
if buy > p: # current price is less than last buy
buy = p # buy at p
else:
tmp = p - buy - fee
if tmp > 0: # have profit
res += tmp
buy = p - fee
return res
http://www.programmersought.com/article/168957256/
Given an array, the ith element in the array is the price of the first day of the stock. Now try to buy and sell stocks to get the maximum value.
If you use greedy thoughts to solve this problem, you should note that greedy choice has nothing to do with the resulting sub-problems. Therefore, you can only determine when you should buy, and you can't determine when to sell (because there is no standard to measure how much it is best to earn). Therefore, we need to develop a judgment standard to measure when we should buy.
If for a monotonically increasing number of columns, the optimal solution must be the maximum-minimum value. But if the specified interval is not monotonously rising, then assuming that there is a value larger than the interval minimum and a value smaller than the interval maximum, the interval can be divided into two parts, and the two parts are The sum of the respective maximum-minimum values is greater than the original interval maximum-minimum.
In this way, the array is traversed from the left side, and the original interval is continuously subdivided according to the criteria for selecting the purchase, and the optimal solution is finally obtained.
If you use greedy thoughts to solve this problem, you should note that greedy choice has nothing to do with the resulting sub-problems. Therefore, you can only determine when you should buy, and you can't determine when to sell (because there is no standard to measure how much it is best to earn). Therefore, we need to develop a judgment standard to measure when we should buy.
If for a monotonically increasing number of columns, the optimal solution must be the maximum-minimum value. But if the specified interval is not monotonously rising, then assuming that there is a value larger than the interval minimum and a value smaller than the interval maximum, the interval can be divided into two parts, and the two parts are The sum of the respective maximum-minimum values is greater than the original interval maximum-minimum.
In this way, the array is traversed from the left side, and the original interval is continuously subdivided according to the criteria for selecting the purchase, and the optimal solution is finally obtained.
- Since it is judged when to buy, the sell operation is performed before the buy is executed.
- Pay attention to the initialization of the maximum and minimum values of the next interval when selling
- Since it is judged when to buy, it will not be sold before there is a new purchase, so at the end of the cycle, the last sale is made
贪心选择的关键是找到一个最大后是不是能够卖掉stock,重新开始寻找买入机会。比如序列1 3 2 8,如果发现2小于3就完成交易买1卖3,此时由于fee=2,(3-1-fee)+(8-2-fee)<(8-1-fee),所以说明卖早了,令max是当前最大price,当(max-price[i]>=fee)时可以在max处卖出,且不会存在卖早的情况,再从i开始重新寻找买入机会
curProfit记录了当前一次交易能得到的最大收益,只有当maxP-prices[i]>=fee时,才将curProfit累加到总的收益中。最后一次交易不需要考虑是否早卖了,所以直接累加最后一次的curProfit。
int maxProfit(vector<int>& prices, int fee) {
int profit=0;
int curProfit=0;
int minP=prices[0];
int maxP=prices[0];
int i;
for(i=1;i<prices.size();i++){
minP=min(minP,prices[i]);
maxP=max(maxP,prices[i]);
curProfit=max(curProfit,prices[i]-minP-fee);
if((maxP-prices[i])>=fee){//can just sell the stock at maxP day.
profit+=curProfit;
curProfit=0;
minP=prices[i];
maxP=prices[i];
}
}
return profit+curProfit;//the last trade have to be made if there is some profit
}
https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/discuss/108884/JavaC++-Clean-Code-(DPGreedy)
- buy in - when current price higher than previous lowest point by more than amount of transaction fee, and set current price as highest point;
- sale out - when current price lower than prevous highest point by more than amount of transaction fee, and reset lowest, highest
- update highest - only if highest is set;
- update lowest - every day
public int maxProfit(int[] p, int fee) {
int profit = 0;
Integer lo = null, hi = null, n = p.length;
for (int i = 0; i < n; i++) {
if (lo != null && hi == null && p[i] - lo > fee) hi = p[i]; // buy in
if (hi != null && p[i] > hi) hi = p[i]; // update highest
if (hi != null && (hi - p[i] > fee || i == n - 1)) { // sale out
profit += hi - lo - fee;
hi = null;
lo = null;
}
lo = lo != null ? Math.min(lo, p[i]) : p[i]; // update lowest
}
return profit;
}
https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/discuss/108873/faster-than-the-%22max%22-solution-and-very-clear-answer
public int maxProfit(int[] prices, int fee) {
int state = 0, profit = 0, prev = prices[0];
for (int price : prices) {
state += price - prev;
if (state > fee) {
profit += state - fee;
state = fee;
} else if (state < 0) {
state = 0;
}
prev = price;
}
return profit;
}
Explanation:
. state is a switch variable
. when state >= fee, all incoming positive price movement will become profit
. when state <= 0, that, all incoming negative price movement will be discarded
TLE
public int maxProfit2(int[] prices, int fee) {
if (prices == null || prices.length < 2) {
return 0;
}
int len = prices.length;
int[] sell = new int[len], buy = new int[len];
int max = Integer.MIN_VALUE;
for (int i = 0; i < prices.length; i++) {
buy[i] = -prices[i];
for (int j = 0; j < i; j++) {
sell[i] = Math.max(sell[i], buy[j] + prices[i] - fee);
buy[i] = Math.max(buy[i], sell[j] - prices[i]);
max = Math.max(max, sell[i]);
}
}
return max;
}
https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/discuss/108870/Most-consistent-ways-of-dealing-with-the-series-of-stock-problems
this is a repost of my original post here with updated solutions for this problem (714. Best Time to Buy and Sell Stock with Transaction Fee). If you are only looking for solutions, you can go directly to each section in part
II -- Applications to specific cases
.
Up to this point, I believe you have finished the following series of stock problems:
- 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
For each problem, we've got a couple of excellent posts explaining how to approach it. However, most of the posts failed to identify the connections among these problems and made it hard to develop a consistent way of dealing with this series of problems. Here I will introduce the most generalized solution applicable to all of these problems, and its specialization to each of the six problems above.
I -- General cases
The idea begins with the following question: Given an array representing the price of stocks on each day, what determines the maximum profit we can obtain?
Most of you can quickly come up with answers like "it depends on which day we are and how many transactions we are allowed to complete". Sure, those are important factors as they manifest themselves in the problem descriptions. However, there is a hidden factor that is not so obvious but vital in determining the maximum profit, which is elaborated below.
First let's spell out the notations to streamline our analyses. Let
prices
be the stock price array with length n
, i
denote the i-th
day (i
will go from 0
to n-1
), k
denote the maximum number of transactions allowed to complete, T[i][k]
be the maximum profit that could be gained at the end of the i-th
day with at most k
transactions. Apparently we have base cases: T[-1][k] = T[i][0] = 0
, that is, no stock or no transaction yield no profit (note the first day has i = 0
so i = -1
means no stock). Now if we can somehow relate T[i][k]
to its subproblems like T[i-1][k], T[i][k-1], T[i-1][k-1], ...
, we will have a working recurrence relation and the problem can be solved recursively. So how do we achieve that?
The most straightforward way would be looking at actions taken on the
i-th
day. How many options do we have? The answer is three: buy, sell, rest. Which one should we take? The answer is: we don't really know, but to find out which one is easy. We can try each option and then choose the one that maximizes our profit, provided there are no other restrictions. However, we do have an extra restriction saying no multiple transactions are allowed at the same time, meaning if we decide to buy on the i-th
day, there should be 0
stock held in our hand before we buy; if we decide to sell on the i-th
day, there should be exactly 1
stock held in our hand before we sell. The number of stocks held in our hand is the hidden factor mentioned above that will affect the action on the i-th
day and thus affect the maximum profit.
Therefore our definition of
T[i][k]
should really be split into two: T[i][k][0]
and T[i][k][1]
, where the former denotes the maximum profit at the end of the i-th
day with at most k
transactions and with 0
stock in our hand AFTER taking the action, while the latter denotes the maximum profit at the end of the i-th
day with at most k
transactions and with 1
stock in our hand AFTER taking the action. Now the base cases and the recurrence relations can be written as:- Base cases:
T[-1][k][0] = 0, T[-1][k][1] = -Infinity
T[i][0][0] = 0, T[i][0][1] = -Infinity
- Recurrence relations:
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 cases,
T[-1][k][0] = T[i][0][0] = 0
has the same meaning as before while T[-1][k][1] = T[i][0][1] = -Infinity
emphasizes the fact that it is impossible for us to have 1
stock in hand if there is no stock available or no transactions are allowed.
For
T[i][k][0]
in the recurrence relations, the actions taken on the i-th
day can only be rest and sell, since we have 0
stock in our hand at the end of the day. T[i-1][k][0]
is the maximum profit if action rest is taken, while T[i-1][k][1] + prices[i]
is the maximum profit if action sell is taken. Note that the maximum number of allowable transactions remains the same, due to the fact that a transaction consists of two actions coming as a pair -- buy and sell. Only action buy will change the maximum number of transactions allowed (well, there is actually an alternative interpretation, see my comment below).
For
T[i][k][1]
in the recurrence relations, the actions taken on the i-th
day can only be rest and buy, since we have 1
stock in our hand at the end of the day. T[i-1][k][1]
is the maximum profit if action rest is taken, while T[i-1][k-1][0] - prices[i]
is the maximum profit if action buy is taken. Note that the maximum number of allowable transactions decreases by one, since buying on the i-th
day will use one transaction, as explained above.
To find the maximum profit at the end of the last day, we can simply loop through the
prices
array and update T[i][k][0]
and T[i][k][1]
according to the recurrence relations above. The final answer will be T[i][k][0]
(we always have larger profit if we end up with 0
stock in hand).II -- Applications to specific cases
The aforementioned six stock problems are classified by the value of
k
, which is the maximum number of allowable transactions (the last two also have additional requirements such as "cooldown" or "transaction fee"). I will apply the general solution to each of them one by one.
Case I:
k = 1
For this case, we really have two unknown variables on each day:
T[i][1][0]
and T[i][1][1]
, and the recurrence relations say: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])
where we have taken advantage of the base case
T[i][0][0] = 0
for the second equation.
It is straightforward to write the
O(n)
time and O(n)
space solution, based on the two equations above. However, if you notice that the maximum profits on the i-th
day actually only depend on those on the (i-1)-th
day, the space can be cut down to O(1)
. Here is the space-optimized 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;
}
Now let's try to gain some insight of the solution above. If we examine the part inside the loop more carefully,
T_i11
really just represents the maximum value of the negative of all stock prices up to the i-th
day, or equivalently the minimum value of all the stock prices. As for T_i10
, we just need to decide which action yields a higher profit, sell or rest. And if action sell is taken, the price at which we bought the stock is T_i11
, i.e., the minimum value before the i-th
day. This is exactly what we would do in reality if we want to gain maximum profit. I should point out that this is not the only way of solving the problem for this case. You may find some other nice solutions here.
Case II:
k = +Infinity
If
k
is positive infinity, then there isn't really any difference between k
and k - 1
(wonder why? see my comment below), which implies T[i-1][k-1][0] = T[i-1][k][0]
and T[i-1][k-1][1] = T[i-1][k][1]
. Therefore, we still have two unknown variables on each day: T[i][k][0]
and T[i][k][1]
with k = +Infinity
, and the recurrence relations say: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])
where we have taken advantage of the fact that
T[i-1][k-1][0] = T[i-1][k][0]
for the second equation. The O(n)
time and O(1)
space solution 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 caching of the old values of
T_ik0
, that is, the variable T_ik0_old
, is unnecessary. Special thanks to 0x0101 and elvina for clarifying this.)
This solution suggests a greedy strategy of gaining maximum profit: as long as possible, buy stock at each local minimum and sell at the immediately followed local maximum. This is equivalent to finding increasing subarrays in
prices
(the stock price array), and buying at the beginning price of each subarray while selling at its end price. It's easy to show that this is the same as accumulating profits as long as it is profitable to do so, as demonstrated in this post.
Case III:
k = 2
Similar to the case where
k = 1
, except now we have four variables instead of two on each day: T[i][1][0]
, T[i][1][1]
, T[i][2][0]
, T[i][2][1]
, and the recurrence relations are: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])
where again we have taken advantage of the base case
T[i][0][0] = 0
for the last equation. The O(n)
time and O(1)
space solution is as follows:public int maxProfit(int[] prices) {
int T_i10 = 0, T_i11 = Integer.MIN_VALUE;
int 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;
}
which is essentially the same as the one given here.
Case IV:
k is arbitrary
This is the most general case so on each day we need to update all the maximum profits with different
k
values corresponding to 0
or 1
stocks in hand at the end of the day. However, there is a minor optimization we can do if k
exceeds some critical value, beyond which the maximum profit will no long depend on the number of allowable transactions but instead will be bound by the number of available stocks (length of the prices
array). Let's figure out what this critical value will be.
A profitable transaction takes at least two days (buy at one day and sell at the other, provided the buying price is less than the selling price). If the length of the
prices
array is n
, the maximum number of profitable transactions is n/2
(integer division). After that no profitable transaction is possible, which implies the maximum profit will stay the same. Therefore the critical value of k
is n/2
. If the given k
is no less than this value, i.e., k >= n/2
, we can extend k
to positive infinity and the problem is equivalent to Case II
.
The following is the
O(kn)
time and O(k)
space solution. Without the optimization, the code will be met with TLE 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 found in this post. Here I used backward looping for the
T
array to avoid using temporary variables. It turns out that it is possible to do forward looping without temporary variables, too.
Case V:
k = +Infinity but with cooldown
This case resembles
Case II
very much due to the fact that they have the same k
value, except now the recurrence relations have to be modified slightly to account for the "cooldown" requirement. The original recurrence relations for Case II
are given byT[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 with "cooldown", we cannot buy on the
i-th
day if a stock is sold on the (i-1)-th
day. Therefore, in the second equation above, instead of T[i-1][k][0]
, we should actually use T[i-2][k][0]
if we intend to buy on the i-th
day. Everything else remains the same and the new recurrence relations areT[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])
And here is the
O(n)
time and O(1)
space solution: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 nice solution here with thinking process, which turns out to be the same as the one above.
Case VI:
k = +Infinity but with transaction fee
Again this case resembles
Case II
very much as they have the same k
value, except now the recurrence relations need to be modified slightly to account for the "transaction fee" requirement. The original recurrence relations for Case II
are given byT[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 now we need to pay some fee (denoted as
fee
) for each transaction made, the profit after buying or selling the stock on the i-th
day should be subtracted by this amount, therefore the new recurrence relations will be eitherT[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)
or
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 we have two options as for when to subtract the
fee
. This is because (as I mentioned above) each transaction is characterized by two actions coming as a pair - - buyand sell. The fee can be paid either when we buy the stock (corresponds to the first set of equations) or when we sell it (corresponds to the second set of equations). The following are the O(n)
time and O(1)
space solutions corresponding to these two options, where for the second solution we need to pay attention to possible overflows.
Solution I -- pay the fee when buying the 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 the fee when selling the 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 summary, the most general case of the stock problem can be characterized by three factors, the ordinal of the day
i
, the maximum number of allowable transactions k
, and the number of stocks in our hand at the end of the day. I have shown the recurrence relations for the maximum profits and their termination conditions, which leads to the O(nk)
time and O(k)
space solution. The results are then applied to each of the six cases, with the last two using slightly modified recurrence relations due to the additional requirements. I should mention that peterleetcode also introduced a nice solution here which generalizes to arbitrary k
values. If you have a taste, take a look.