因为所关心的是利润,所以以利润为状态。
一,一般情况分析
以T[i][k]来表示在第i天实现了k次交易得到的利润。所以当给定i个price,和最大k次交易的时候,所得T[i][k]即是结果。如此定义我们有,T[-1][k] = T[i][0] = 0 (第一天之前和未交易时所得利润均为0)。
那么T[i][k]的变化与什么有关呢?
一个人当天的操作只有三种,buy,rest,sell。在这个系列问题的设定下,一个人手上最多只能同时持一股。所以在一个人做完当天的操作之后,T只有两种状态:
- 手上0股所得利润T[i][k][0]。
- 手上1股所得利润T[i][k][1]。
下面来看看T是如何变化的:
当天交易完T[i][k][0]和什么有关呢?0表示操作完手上没有股票了,这有可能是今天卖出或者是之前卖出而今天rest得到的结果,可以写出T[i][k][0]的变化式:
T[i][k][0] = max(T[i-1][k][1] + prices[i], T[i-1][k][0])
- 第一项的意思是,今天之前手上有一股,而我在今天选择卖出,所以今天会收入对应股价的利润,注意由于T表示利润,所以成本已经减掉了,新的利润直接加上股价即可,由于卖出不影响交易数量,所以交易数量保持k不变。
- 第二项的意思是,今天之前我手上一股也没有,但我今天选择佛系,不买入,所以利润不变。
当天交易完T[i][k][1]和什么有关呢?1表示操作完手上还有一股,那么这一股有可能是今天买入或者是之前买入而今天rest得到的结果,可以写出T[i][k][1]的变化式:
T[i][k][1] = max(T[i-1][k-1][0] - prices[i], T[i-1][k][1])
- 第一项的意思是,今天之前我的手上没有股票,今天购入了一股,证明我今天是在已得利润上花钱买了一股,所以是利润减今天的股价,我的交易数量也因为买入一股而从k-1增长到k。
- 第二项的意思是,今天我选择不买不卖,所以利润没有变化,我的交易数量也没有变化。
遍历股价,最终得到的结果T[i][k][0]即是我们需要的最大利润。
二,特殊情况分析
其实各个题型的变化实际在变的内容都是交易数量,或是在交易的内容中加入一些特殊的变化,由此产生了leetcode中的6个Stock系列的题目。所以接下来我们看看一般分析是怎样化用到Stock系列的题型中的。
case 1:只交易一次 (k=1),对应leetcode第121题 Best Time to Buy and Sell Stock
T[i][k][0] = max(T[i-1][k][1] + prices[i], T[i-1][k][0]) ===> T[i][1][0] = max(T[i-1][1][1] + prices[i], T[i-1][1][0])
T[i][k][1] = max(T[i-1][k-1][0] - prices[i], T[i-1][k][1]) ===> T[i][1][1] = max(T[i-1][0][0] - prices[i], T[i-1][1][1]) ===> T[i][1][1] = max(-prices[i], T[i-1][1][1]) (因为T[i-1][0][0] = 0)
翻译成代码:
int maxProfit(vector<int>& prices) {
int T_i10 = 0, T_i11 = INT_MIN;
for (auto price : prices) {
T_i10 = max(T_i10, T_i11 + price);
T_i11 = max(T_i11, -price);
}
return T_i10;
}
case 2:可交易无数次,对应leetcode第122题 Best Time to Buy and Sell Stock II
当交易次数为无数次时,k就不会明显地影响到利润了,所得最大利润更多的取决于可供交易的股价数量。
原解释如下:
Sorry for the confusion. So there are two interpretations here. First from a mathematical point of view,
limit(k)
is the same aslimit(k-1)
whenk
approaches+infinity
. Second, more relevant to this problem, as I said for the case of arbitraryk
, whenk
is sufficiently large, the maximum profits will on longer depend onk
but be bound by the number of available stocks. This means ifk
goes to+infinity
, the profits won't change if you increase or decrease the value ofk
, that is,T[i][k][0]
will be the same asT[i][k-1][0]
andT[i][k][1]
the same asT[i][k-1][1]
. Hope this clarifies things a little bit
变化式变为:
T[i][k][0] = max(T[i-1][k][1] + prices[i], T[i-1][k][0]) ===> T[i][k][0] = max(T[i-1][k][1] + prices[i], T[i-1][k][0])
T[i][k][1] = max(T[i-1][k-1][0] - prices[i], T[i-1][k][1]) ===> T[i][k][1] = max(T[i-1][k][0] - prices[i], T[i-1][k][1])
翻译成代码:
int maxProfit(vector<int>& prices) {
int T_ik0 = 0, T_ik1 = INT_MIN;
for (auto price : prices) {
int T_ik0_old = T_ik0;
T_ik0 = max(T_ik0, T_ik1 + price);
T_ik1 = max(T_ik1, T_ik0_old - price);
}
return T_ik0;
}
case 3:可交易规定次数,对应leetcode第123和124题 Best Time to Buy and Sell Stock III, Best Time to Buy and Sell Stock IV
与交易无数次不同,这种题型是交易有限次数,k对最大利润的影响需要考虑,所以之前将k-1简化为k的行为不可以再次出现啦。当然这次的式子变化也是最朴素的,如下:
T[i][k][0] = max(T[i-1][k][1] + prices[i], T[i-1][k][0])
T[i][k][1] = max(T[i-1][k-1][0] - prices[i], T[i-1][k][1])
就,没变,贴在这只不过不需要翻回去了,,,
翻译成代码:
int maxProfit(int k, vector<int>& prices) {
if (prices.empty()) return 0;
vector<int> T_ik0(k + 1);
vector<int> T_ik1(k + 1, INT_MIN);
for (auto price : prices) {
for (int j = k; j > 0; j--) {
T_ik0[j] = max(T_ik0[j], T_ik1[j] + price);
T_ik1[j] = max(T_ik1[j], T_ik0[j - 1] - price);
}
}
return T_ik0[k];
}
case 4:可交易无数次,但是有cool down,对应leetcode第309题,Best Time to Buy and Sell Stock with Cooldown
这一题就是case2的变种,所以变化式会在case2的基础上有略微的改变
T[i][k][0] = max(T[i-1][k][1] + prices[i], T[i-1][k][0]) ===> T[i][k][0] = max(T[i-1][k][1] + prices[i], T[i-1][k][0])
T[i][k][1] = max(T[i-1][k-1][0] - prices[i], T[i-1][k][1]) ===> T[i][k][1] = max(T[i-1][k][0] - prices[i], T[i-1][k][1]) ===> T[i][k][1] = max(T[i-2][k][0] - prices[i], T[i-1][k][1]) (因为cool down)
翻译成代码:
int maxProfit(vector<int>& prices) {
int T_ik0_pre = 0, T_ik0 = 0, T_ik1 = INT_MIN;
for (int price : prices) {
int T_ik0_old = T_ik0;
T_ik0 = max(T_ik0, T_ik1 + price);
T_ik1 = max(T_ik1, T_ik0_pre - price);
T_ik0_pre = T_ik0_old;
}
return T_ik0;
}
case 5:可交易无数次,但是有transaction fee,对应leetcode第714题,Best Time to Buy and Sell Stock with Transaction Fee
这一题也是case2的变种,所以变化式会在case2的基础上有略微的改变,交易费可以在买入的时候减去,也可以在卖出的时候减去,这样变化后的式子就有两种情况:
买入时减fee
T[i][k][0] = max(T[i-1][k][1] + prices[i], T[i-1][k][0]) ===> T[i][k][0] = max(T[i-1][k][1] + prices[i], T[i-1][k][0])
T[i][k][1] = max(T[i-1][k-1][0] - prices[i], T[i-1][k][1]) ===> T[i][k][1] = max(T[i-1][k][0] - prices[i] - fee, T[i-1][k][1])
卖出时减fee
T[i][k][0] = max(T[i-1][k][1] + prices[i], T[i-1][k][0]) ===> T[i][k][0] = max(T[i-1][k][1] + prices[i] - fee, T[i-1][k][0])
T[i][k][1] = max(T[i-1][k-1][0] - prices[i], T[i-1][k][1]) ===> T[i][k][1] = max(T[i-1][k][0] - prices[i], T[i-1][k][1])
翻译成代码:
买入时减fee
int maxProfit(vector<int>& prices, int fee) {
int T_ik0 = 0, T_ik1 = INT_MIN;
for (auto price : prices) {
int T_ik0_old = T_ik0;
T_ik0 = max(T_ik0, T_ik1 + price);
T_ik1 = max(T_ik1, T_ik0_old - price - fee);
}
return T_ik0;
}
卖出时减fee
int maxProfit(vector<int>& prices, int fee) {
if (prices.empty()) return 0;
int T_ik0 = 0, T_ik1 = -prices[0];
for (auto price : prices) {
int T_ik0_old = T_ik0;
T_ik0 = max(T_ik0, T_ik1 + price - fee);
T_ik1 = max(T_ik1, T_ik0_old - price);
}
return T_ik0;
}
三、结论
懒得写了哈哈哈。
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 transactionsk
, 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 theO(nk)
time andO(k)
space solution.