给定一个整数数组 prices
,其中第 i
个元素代表了第 i
天的股票价格 ;非负整数 fee
代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每次交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
返回获得利润的最大值。
示例 1:
输入: prices = [1, 3, 2, 8, 4, 9], fee = 2 输出: 8 解释: 能够达到的最大利润: 在此处买入 prices[0] = 1 在此处卖出 prices[3] = 8 在此处买入 prices[4] = 4 在此处卖出 prices[5] = 9 总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8.
解决方案——动态规划算法
先来谈谈动态规划:
该算法针对于解决多阶段决策问题。所谓多阶段决策问题,指的是一类可以按照时间顺序或空间顺序拆分成若干个相互联系阶段的问题,在每个阶段都要做出决策,从而确定出下一阶段的状态,后续阶段的决策依赖于之前的决策结果,但不影响之前阶段的决策结果。全部的决策过程可以组成一个决策序列,使得整个活动的总体效果达到最优。
解决动态规划问题可以从两方面入手:阶段和决策。
(1) 阶段:将原问题的求解过程按照时间或者空间特征来进行分解,依次对每个阶段进行求解,当前阶段的解依赖于之前多阶段的解,但是当前阶段的解无法修改后续阶段的解,即各阶段无后效性。因为一个阶段的解如果存在后效性,则证明此时的阶段划分方法不合理。不断迭代求解各阶段的解,从而获取原问题的最终解。
(2) 决策:各阶段在划分之后所采取的动作叫做决策。在实际求解过程中,决策的种类往往在一定范围内。不同的决策往往会影响下一阶段的求解状态。动态规划算法的所有决策应构成最优策略,最优策略的子策略也应该保证其最优性。
首先考虑:不收取交易费的情况(思路源于:https://blog.csdn.net/jiyanfeng1/article/details/41691889)
对于价格 price=[5, 1, 2, 3, 4, 0],我们最大的收益方式就是在第2天买入(即价格为1时),第5天卖出(即价格为4时)。由于没有交易的手续费,因此:
一,在1买入,4卖出;
二,在1买入,2卖出同时买入,3卖出同时买入,4卖出;
这两种操作的收益是相同。即:
int maxProfit(vector<int> &prices) {
int p = 0;
for(int i = 1; i < prices.size() ; ++i) {
int delta = prices[i] - prices[i-1];
if(delta > 0 ) {
p += delta;
}
}
return p;
}
这里我们的原则就是我们的交易行为是有所收益的,即
if(delta > 0 )
这里面的“阶段”就是每天的价格。“决策”就是delta>0
同理,对于收取手续费的交易,我们也有找到那个交易的最优解
这里“阶段”依然是每天的价格,但是“决策”要添加一个考虑因素,那就是“fee”
每一天,都有两种选择,要么就是“买入”和“不买入”,要么就是“卖出”和“不卖出”。因此,用暴力枚举的方式,我们需要考虑2的n次幂种情况(假定price中共有n天的价格)。这是不可取的。我们可以选择用空间换时间。
我们在买卖的过程中只会存在两种状态,持有股票和不持有股票。那么,我们设置两个数组,分别记录出两种状态下每一天的最优解状况。由于最终我们的状态是不持有股票,因此记录不持有状态的那个数组中的最后一位即是我们的最优解。详细解释如下:
两个数组 have[i] 和 dont_have[i] 分别记录第 i 天持有和不持有股票时的最优解(由于动态规划算法无后效性,因此子问题的解也是最优解)
对于 have[i]
第 i 天买入的情况:收益为 dont_have[i-1] -prices[i] (此时说明前一天不持有股票)
第 i 天继续持仓的情况:收益为 have[i-1] (此时说明前一天也持有)
对于 dont_have[i]
对于第 i 天卖出的情况,算上每笔交易的手续费,此时收益为 have[i-1] +prices[i]-fee
对于继续不买入的情况,收益与前一天相同,为 dont_have[i-1]
对于每一天,我们要求的是最优解,因此要选择每一天收益的最大值,即:
have[i] =max(dont_have[i-1] -prices[i] , have[i-1])
dont_have[i] =max(have[i-1] +prices[i]-fee,dont_have[i-1] )
最后一天手上不持有股票是为最大收益,因此我们的解为 dont_have[prices.size()-1] 的值。代码如下(时间复杂度O(n)):
int maxProfit(vector<int>& prices, int fee) {
vector<int> have(prices.size(), 0);
vector<int> dont_have(prices.size(), 0);
have[0] = have[0] - prices[0];
for (int i = 1; i < prices.size(); ++i) {
have[i] = max(have[i - 1], dont_have[i - 1] - prices[i]);
dont_have[i] = max(dont_have[i - 1], have[i - 1] + prices[i] - fee);
}
return dont_have[prices.size() - 1];
}
有时候问题解决了不一定就代表任务结束了,如果面试时候你的代码比别人的时间更快,占用的内存更小,你就更有优势。所以,我们来考虑一下如何优化上述代码:
我们很容易看出其实每次迭代只依赖于i-1时的状态,所以我们不需要保存每一天的状态。
int maxProfit(vector<int>& prices, int fee) {
int have = 0 - prices[0];
int dont_have = 0;
int dont_have_temp;
int have_temp;
for (int i = 1; i < prices.size(); ++i) {
have_temp = have;
dont_have_temp = dont_have;
have = max(have_temp, dont_have_temp - prices[i]);
dont_have = max(dont_have_temp, have_temp + prices[i] - fee);
}
return dont_have;
}