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 at most k transactions. Note: You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).
解题思路:
这是一个动态规划问题。首先分析问题与子问题之间的依赖关系。
问题可以分解成,如果已知在i天,最大交易次数为k次时的最大收益,如何求得在i+1天时,最大交易次数为k次的最大收益。最后一天一定是将股票卖出,但我们不能光考虑卖出股票的收益记录,因为卖出股票的收益基于上一次买入股票的收益。而在特定的某一天中,有三种操作状态:买股票,卖股票,什么也不做。一次连续的买卖构成一次交易操作。
为了计算收益而又能够不需要考虑天数差与收益之间的联系,在计算收益的时候,直接将某一日的交易金额作为收入。即在买入股票时,当天收益为股票价格的负数,卖出股票时,当天收益就是股票的价格。
因此,把状态转移方程写出来就是:
第i天中第k次买股票所得收益 = max{第i-1天中第k-1次卖股票所得收益-第i天的股价,第i-1天中第k次买股票所得收益}
第i天中第k次卖股票所得收益 = max{第i-1天中第k次买股票所得收益+第i天的股价,第i-1天中第k次卖股票所得收益}
用profit[i][k][2]存储第i天第k次交易所得的最大收益,最低维的二维数组用来存储第k次交易中买和卖分别对应的收益。由于当天这一天收益基于上一天,其实只需要用profit[k][2]来记录当前这一天收益的更新情况即可。
还有几个值得注意的地方是:
1. 因为第k次交易基于第k-1次交易的结果,为了防止结果被覆盖,采用从后向前(k->0)的顺序遍历更新收益。当然也可以用另一个数组先存起来再计算,但这会需要额外的空间。
2. 因为求第i天第k次卖的结果依赖于第i-1天第k次买的结果,如果先求第i天第k次买的结果会导致第i-1天第k次买的结果被覆盖。因此每次交易得先求卖结果再求买结果。
3. 由于边界检查需要检查数组是否越界,为了统一计算,将k次交易数组的大小设置成了k+1,第0个位置存放初始化数据,即profit[k][0] = INT_MIN,确保初始化买股票的金额一定会被更新(买股票时股票的价格小于INT_MAX),profit[k][1] = 0,确保卖股票的收益一定会被更新(卖股票时为了获取最大收益,收益一定为正)
4. 因为n天中股票最多交易n/2次,因此当k大于等于n/2时,问题退化为Best Time to Buy and Sell Stock II
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
int size(prices.size());
if(size <= 1 || k <= 0)
return 0;
if(k >= size/2)
return bestProfit(prices);
vector<vector<int>> profit(k+1, vector<int>(2, 0));
for(int i = 0; i <= k; ++i)
profit[i][0] = INT_MIN;
for(int i = 0; i < size; ++i) {
for(int j = k; j > 0; --j) {
profit[j][1] = max(profit[j][1], profit[j][0]+prices[i]);
profit[j][0] = max(profit[j][0], profit[j-1][1]-prices[i]);
}
}
int result;
for(int i = 1; i <= k; ++i) {
result = max(result, profit[i][1]);
}
return result;
}
int bestProfit(vector<int>& prices) {
int result(0);
for(int i = 1; i < prices.size(); ++i) {
int delta(prices[i] - prices[i-1]);
if(prices[i] - prices[i-1] > 0) {
result += delta;
}
}
return result;
}
};