目录:
前言
打怪升级:第80天 |
---|
![](https://i-blog.csdnimg.cn/blog_migrate/838d841203cfea9388707a3da24098bc.png)
买卖股票的最佳时机
买卖股票的最佳时机:简单
题解1:找出最值区间
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n=prices.size();
int max=prices[0];
int min=prices[0];
int sum=0;
for(int i=1; i<n; ++i)
{
if(prices[i] < min) max=min=prices[i];// 最大值需要在最小值之后
if(prices[i] > max) max=prices[i];
if(max-min>sum) sum=max-min;
}
return sum;
}
};
题解2:问题转化:最大子序和
class Solution {
public:
int maxProfit(vector<int>& prices) {
int ret=0;
int sum=0;
for(int i=1; i<prices.size(); ++i) // 转换为求解最大子序和
{
int dev = prices[i] - prices[i-1];
sum = max(dev, dev + sum);
ret = max(ret, sum);
}
return ret;
}
};
题解3:动态规划
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
vector<vector<int> >dp(n, vector<int>(2, 0));
dp[0][0] = -prices[0];
for(int i=1; i<n; ++i)
{
dp[i][0] = max(dp[i-1][0], -prices[i]);
dp[i][1] = max(dp[i-1][1], prices[i] + dp[i-1][0]);
}
return dp[n-1][1];
}
};
买卖股票的最佳时机 II
题解1:动态规划
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
vector<vector<int> >dp(n, vector<int>(2, 0));
dp[0][0] = -prices[0];
for(int i=1; i<n; ++i)
{
dp[i][0] = max(dp[i-1][0], dp[i-1][1] - prices[i]);
dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i]);
}
return dp[n-1][1];
}
};
对比两道题的状态转移方程:
题解2:贪心
这道题允许我们多次买卖,只要有的赚就可以买,这里有很明显的贪心思想,下面我们画图理解:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int ret=0;
for(int i=1; i<prices.size(); ++i)
ret += max(0, prices[i] - prices[i-1]); // 今天卖出前一天的股票,收益为正
return ret;
}
};
在很多情况下,贪心都是一种很便利快速的解决问题的方法,不过想要看出这道题是否可以使用贪心,这对于我们还是有很大挑战的。
买卖股票的最佳时机含冷冻期
动态规划
买入:今天手里有股票,可以是昨天的没有卖,也可以是今天新买的;
可交易:现在手中没有股票,可以是今天没有买,也可以是冷冻期刚刚结束;
冷冻期:说明今天卖出了股票,进入一天冷冻期;
状态很多的情况下,画出状态机进行分析:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
vector<vector<int> >dp(n, vector<int>(3, 0)); // 三种状态 -- 买入、可交易、冷冻期
dp[0][0] = -prices[0];
for(int i=1; i<n; ++i)
{
dp[i][0] = max(dp[i-1][0], dp[i-1][1] - prices[i]);
dp[i][1] = max(dp[i-1][1], dp[i-1][2]);
dp[i][2] = dp[i-1][0] + prices[i];
}
return max(dp[n-1][1], dp[n-1][2]);
}
};
买卖股票的最佳时机含手续费
动态规划
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int n = prices.size();
vector<vector<int> >dp(n, vector<int>(2, 0));
dp[0][0] = -prices[0];
// 不要纠结于会出现一只股票卖出多次的情况,既会减去多次fee,因为这里只是赋值,实际的dp[i-1][0] 和 prices[i]都没有改变,因此再次售卖也只会减去一次fee
for(int i=1; i<n; ++i)
{
dp[i][0] = max(dp[i-1][0], dp[i-1][1] - prices[i]);
dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i] - fee); // 卖出的时候付手续费 -- 也可以买入的时候付
}
return dp[n-1][1];
}
};
买卖股票的最佳时机 III
动态规划
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
vector<vector<int>>f(n, vector<int>(3, -prices[0])); // 买入状态
vector<vector<int>>g(n, vector<int>(3, 0)); // 卖出状态
for(int i=1; i<n; ++i)
{
for(int j=0; j<3; ++j)
{
f[i][j] = max(f[i-1][j], g[i-1][j] - prices[i]);
g[i][j] = g[i-1][j];
if(j > 0) // 判断j是否合理
g[i][j] = max(g[i-1][j], f[i-1][j-1] + prices[i]);
}
}
return max(g[n-1][0], max(g[n-1][1], g[n-1][2]));
}
};
买卖股票的最佳时机 IV
本题与上一题的唯一区别是:上一题最多交易2次,本题最多交易k次,我们只需要将上一题中的 3 改为 k + 1即可。
动态规划
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
int n = prices.size();
vector<vector<int> >f(n, vector<int>(k+1, -prices[0]));
vector<vector<int> >g(n, vector<int>(k+1, 0));
for(int i=1; i<n; ++i)
{
for(int j=0; j<k+1; ++j)
{
f[i][j] = max(f[i-1][j], g[i-1][j] - prices[i]);
g[i][j] = g[i-1][j];
if(j > 0)
g[i][j] = max(g[i-1][j], f[i-1][j-1] + prices[i]);
}
}
int ret = 0;
for(auto e : g[n-1]) // 最后一行取最大值
ret = max(ret, e);
return ret;
}
};
总结
-
买卖股票问题与打家劫舍类问题一样,都属于多状态dp问题,既在第 i 个位置时,会有多种状态需要分析,例如:
买卖股票:第 i 天 可以选择 持有股票,也可以不持有股票,
打家劫舍:第 i 家 可以选择 进行偷窃,也可以不进行偷窃。
甚至类似上面的,在第 i 天 时 一共进行了 多少次交易,这就需要再增加更多的状态表示进行区分。 -
上方我们在进行转移方程推导时画了好多图,例如:
这个状态转移图我们称之为:状态机,用来更加清楚地描述各个状态之间相互转换的情况,我们在之后写动态规划类题目时大都可以画出状态机来理清关系。 -
dp[i][0] 只是考虑会买入, dp[i][1]只是考虑会卖出,实际上是否卖出要选择最大值。