1、题目描述
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。
注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
2、示例
输入: [3,2,6,5,0,3], k = 2
输出: 7
解释: 在第 2 天 (股票价格 = 2) 的时候买入,在第 3 天 (股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6-2 = 4 。
随后,在第 5 天 (股票价格 = 0) 的时候买入,在第 6 天 (股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3-0 = 3 。
3、题解
基本思想:动态规划法,对于动态规划题,画出状态转移图。
我们具体到每一天,看看总共有几种可能的「状态」,每一天总共可能是六种状态,每种状态都是由前一天状态转移得到,再找出每个「状态」对应的「选择」。我们要穷举每一天所有「状态」下的最大收益,穷举的目的是根据对应的「选择」更新状态。(注意:已经交易次数是完整一次买入并卖出股票的次数,当买入股票还没有卖出股票不算一次交易算半次)
- 0表示:dp[i++][0][0]第i天,已经交易次数0,手中没有股票状态下的最大收益,0状态可以无操作或者购买第i天股票进入1状态。
- 1表示:dp[i++][0][1]第i天,已经交易次数0,手中持有股票状态下的最大收益,1状态可以无操作或者卖出第i天股票进入2状态,卖出股票交易数加1所以2状态为dp[i++][1][0]。
- 2表示:dp[i++][1][0]第i天,已经交易次数1,手中没有股票状态下的最大收益,2状态可以无操作或者购买第i天股票进入3状态。
- 3表示:dp[i++][1][1]第i天,已经交易次数1,手中持有股票状态下的最大收益,3状态可以无操作或者卖出第i天股票进入4状态,卖出股票交易数加1所以4状态为dp[i++][2][0]。
- 4表示:dp[i++][2][0]第i天,已经交易次数2,手中没有股票状态下的最大收益,4状态可以无操作或者购买第i天股票进入5状态,但是状态5无意义,因为最多交易次数为2,就算购买了股票也不能卖,所以状态5不可能是最后返回的结果,状态5是dp[i++][2][1]。
综上分析:每一天总共可能是六种状态,每种状态都是由前一天状态转移得到,分别是已经交易次数0没有股票、已经交易次数0持有股票、已经交易次数1没有股票、已经交易次数1持有股票、已经交易次数2没有股票、已经交易次数2持有股票。只要求出每一天六种状态下每种状态最大收益就是局部最优,最后返回六种状态下最大值就是全局最优。但是其实某些状态是不存在的,比如第一天已经交易次数1持有股票才第一天不可能已经交易一次,这些状态初始设定为负无穷。
最后返回最后一天下0-4这五种状态下收益中的最大值,因为状态1和3都是买入了股票还没卖出去,所以肯定不是最后的返回结果和状态5情况一样,所以最后返回0、1、3三种状态下收益中的最大值。(0状态是可能存在的,如果股票一直下跌,交易员就不会进行交易,最后返回收益最大值就是0状态)
- dp[i][k][0 or 1]表示第i天,已经交易次数k,手上是否持有股票状态下的最大收益(已经交易次数是完整一次买入并卖出股票的次数,当买入股票还没有卖出股票不算一次交易算半次)
- 起始状态应该是dp[0][0][0]=0:第1天,已经交易次数0,手中没有股票,收益为0。或者dp[0][0][1] = -prices[0]第1天,已经交易次数0,手上持有股票,则买入了第1天的股票收益为-prices[0]
- 遍历第1天之后的每天股票情况
- 计算每一天六种状态下的最大收益,k=0、1、2
- 第i天,已经交易次数k,手中没有股票状态下的最大收益等于max(1、无操作:前一天此状态下的收益;2、卖出股票:前一天的持有股票下的最大收益+今天股票价格,因为卖出股票交易数k+1)。对应代码dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k - 1][1] + prices[i])
- 第i天,已经交易次数k,手中持有股票状态下的最大收益等于max(1、无操作:前一天此状态下的收益;2、买入股票:前一天的没有股票下的最大收益-今天股票价格,因为买入股票还没有卖出交易数不变)。对应代码dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k][0] - prices[i])
- 返回最后一天没有股票状态下交易0次、1次、2次中最大的收益值
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
//基本思想:贪心+动态规划
if (prices.size() == 0)
return 0;
int res = 0;
//当允许交易的次数大于交易天数时,相当于是可以无限交易,使用贪心算法
//否则当k过大,dp分配不了那么多内存空间
if (k > prices.size()/2)
{
for (int i = 1; i < prices.size(); i++)
{
if (prices[i] > prices[i - 1])
res += prices[i] - prices[i - 1];
}
return res;
}
//dp[i][k][0 or 1]表示第i天,已经交易次数k,手上是否持有股票状态下的最大收益
vector<vector<vector<int>>> dp(prices.size(), vector<vector<int>>(k + 1, vector<int>(2, 0)));
//初始状态设置
dp[0][0][0] = 0;
dp[0][0][1] = -prices[0];
for (int i = 1; i < k + 1; i++)
{
dp[0][i][0] = -999999;
dp[0][i][1] = -999999;
}
//遍历第1天之后的每天股票情况,计算每一天的2*(k+1)种状态下的最大收益
for (int i = 1; i < prices.size(); i++)
{
for (int j = 0; j < k + 1; j++)
{
//第i天,已经交易次数j,手中没有股票下的最大收益等于max(1、无操作:前一天此状态下的收益;2、卖出股票:前一天的持有股票下的最大收益+今天股票价格,因为卖出股票交易数j+1)
if (j != 0)
dp[i][j][0] = max(dp[i - 1][j - 1][1] + prices[i], dp[i - 1][j][0]);
//第i天,已经交易次数j,手中持有股票状态下的最大收益等于max(1、无操作:前一天此状态下的收益;2、买入股票:前一天的没有股票下的最大收益 - 今天股票价格,因为买入股票还没有卖出交易数不变)
dp[i][j][1] = max(dp[i - 1][j][0] - prices[i], dp[i - 1][j][1]);
}
}
//最后最大收益一定是手中不持有股票,所以返回最后一天不持有股票的k+1种状态下的最大收益
for (int i = 0; i < k + 1; i++)
res = max(res, dp[prices.size() - 1][i][0]);
return res;
}
};
int main()
{
Solution solute;
vector<int> prices = { 3,2,6,5,0,3 };
int k = 2;
cout << solute.maxProfit(k, prices) << endl;
return 0;
}