Say you have an array for which the ith element is the price of a given stock on day i.
Design an algorithm to find the maximum profit. You may complete as many transactions as you like (ie, buy one and sell one share of the stock multiple times) with the following restrictions:
- You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).
- After you sell your stock, you cannot buy stock on next day. (ie, cooldown 1 day)
Example:
prices = [1, 2, 3, 0, 2] maxProfit = 3 transactions = [buy, sell, cooldown, buy, sell]
摘自:https://discuss.leetcode.com/topic/30431/easiest-java-solution-with-explanations/2
1. Define States
To represent the decision at index i:
buy[i]
: Max profit till index i. The series of transaction is ending with a buy.sell[i]
: Max profit till index i. The series of transaction is ending with a sell.
To clarify:
- Till index
i
, the buy / sell action must happen and must be the last action. It may not happen at indexi
. It may happen ati - 1, i - 2, ... 0
. - In the end
n - 1
, returnsell[n - 1]
. Apparently we cannot finally end up with a buy. In that case, we would rather take a rest atn - 1
. - For special case no transaction at all, classify it as
sell[i]
, so that in the end, we can still returnsell[n - 1]
. Thanks @alex153 @kennethliaoke @anshu2.
2. Define Recursion
buy[i]
: To make a decision whether to buy ati
, we either take a rest, by just using the old decision ati - 1
, or sell at/beforei - 2
, then buy ati
, We cannot sell ati - 1
, then buy ati
, because of cooldown.sell[i]
: To make a decision whether to sell ati
, we either take a rest, by just using the old decision ati - 1
, or buy at/beforei - 1
, then sell ati
.
A great explanation. I think the definitions of buy[i]
and sell[i]
can be refined to these:
-
buy[i]
: Maximum profit which end with buying on dayi
or end
with buying on a day beforei
and takes rest until the dayi
since then. -
sell[i]
: Maximum profit which end with selling on dayi
or end
with selling on a day beforei
and takes rest until the dayi
since then.
So we get the following formula:
buy[i] = Math.max(buy[i - 1], sell[i - 2] - prices[i]);
sell[i] = Math.max(sell[i - 1], buy[i - 1] + prices[i]);
3. Optimize to O(1) Space
DP solution only depending on i - 1
and i - 2
can be optimized using O(1) space.
- Let
b2, b1, b0
representbuy[i - 2], buy[i - 1], buy[i]
- Let
s2, s1, s0
representsell[i - 2], sell[i - 1], sell[i]
Then arrays turn into Fibonacci like recursion:
b0 = Math.max(b1, s2 - prices[i]);
s0 = Math.max(s1, b1 + prices[i]);
4. Write Code in 5 Minutes
First we define the initial states at i = 0
:
- We can buy. The max profit at
i = 0
ending with a buy is-prices[0]
. - We cannot sell. The max profit at
i = 0
ending with a sell is0
.
Here is my solution. Hope it helps!
public int maxProfit(int[] prices) {
if(prices == null || prices.length <= 1) return 0;
int b0 = -prices[0], b1 = b0;
int s0 = 0, s1 = 0, s2 = 0;
for(int i = 1; i < prices.length; i++) {
b0 = Math.max(b1, s2 - prices[i]);
s0 = Math.max(s1, b1 + prices[i]);
b1 = b0; s2 = s1; s1 = s0;
}
return s0;
}
-------------------------------------------------------------------------------------
自己想的O(N^2)的方法。。。
对于某天来说,如果是休息,最大利润和前一天相同,否则的话要做决策,不卖的话跟前一天一样。卖的话依次看看i-1天,i-2天……的情况,
如果i-k天做了卖出操作,那么i-k+1天就要休息,最大利润就是今天价格减去[i-k+2 ,i-1]的最小值,如果i-k-1天做了卖出操作,最大利润就是今天价格减去[i-k+1 ,i-1]的最小值,如果i-k和i-k-1天都没有做卖出操作,最大利润是今天价格减去[i-k,i-1]的最小值。怎么知道哪一天是卖了的呢?另外开辟一个数组记录,dp的过程中如果第i天利润最大不是和i-1天相同,说明是卖出了的,标记这一天就行了。
public int maxProfit(int[] prices)
{
int len=prices.length;
if(len<2)
return 0;
int[] dp=new int[len];
int[] aux=new int[len];
SegmentTree2_1 sgt=new SegmentTree2_1(len);
sgt.build(1, len, 1, prices);
dp[0]=0;
dp[1]=prices[1]>prices[0]?prices[1]-prices[0]:0;
for(int i=2;i<len;i++)
{
int max=dp[i-1];
for(int j=i-2;j>=0;j--)
{
int k;
if(aux[j]==1)
k=j+2;
else if(j-1>=0&&aux[j-1]==1)
k=j+1;
else {
k=j;
}
int stepmin=(int) sgt.query(k+1, i, 1, len, 1);
max=Math.max(max, dp[j]+prices[i]-stepmin);
}
if(max!=dp[i-1])
aux[i]=1;
dp[i]=max;
}
return dp[len-1];
}
}
class SegmentTree2_1
{
private long[] ele;
int cnt=0;
public SegmentTree2_1(int maxn)
{
ele = new long[maxn << 2];
}
public void build(int l, int r, int rt,int[] arr)
{
if(arr.length<1)
return ;
if (l == r)
{
ele[rt] = arr[cnt++];
return;
}
int m = (l + r) >> 1;
build(l, m, rt << 1,arr);
build(m + 1, r, rt << 1 | 1,arr);
PushUP(rt);
}
private void PushUP(int rt)
{
ele[rt] = Math.min(ele[rt << 1] , ele[rt << 1 | 1]);
}
public long query(int L, int R, int l, int r, int rt)
{
if (L <= l && r <= R)
return ele[rt];
int m = (l + r) >> 1;
long ret = Integer.MAX_VALUE;
if (L <= m)
ret = Math.min(ret,query(L, R, l, m, rt << 1));
if (R > m)
ret = Math.min(ret,query(L, R, m + 1, r, rt << 1 | 1));
return ret;
}
}