【leetcode】122. 买卖股票的最佳时机2
题目内容
思路分析
本题是股票系列的第二题,似乎一共有五六道题,做完以后就可以去炒股发家致富了🤣。
本题共有三种思路,拟人(方法一)、动态规划(方法二和三)和贪心(方法四)。其中方法一感觉也可以叫贪心。
感觉拟人和贪心比较容易想到,而动态规划,特别是二维动态规划,思路从何而来?感觉需要看看《算法导论》之类的书学习下相关的理论知识了。
方法一:拟人
模拟人的思考过程。假设当天比前一天价格低,则心里会想“我要是更早以前买入最低点,前一天卖出就好了”。这样就可以保证整个交易过程收益最大。
方法二:动态规划
你可以尽可能地完成更多的交易(多次买卖一支股票)。 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
审题后发现,由于以上规则的存在,因此每天共有两个状态:持仓(手里有股票)/空仓(手里没股票,只有现金)。每天共可以进行三种操作:买/卖/不动。而前一天持仓时只能卖/不动,前一天空仓时只能买/不动。
因此,设cur_cash[i][j]为每天交易完成后持有的现金,i为第i天(从0开始计),j为0代表当天交易完后空仓,j为1代表当天交易完持仓,则可以列出状态转移方程:
cur_cash[i][0] = max(cur_cash[i - 1][0], cur_cash[i - 1][1] + prices[i])
cur_cash[i][1] = max(cur_cash[i - 1][1], cur_cash[i - 1][0] - prices[i])
边界条件:
cur_cash[0][0] = 0
cur_cash[0][1] = -prices[0]
方法三:动态规划优化
方法二中的动态规划每天的状态只与前一天的状态有关,因此与121题题解对动态规划的优化相类似,只需要使用常数个变量记录前一天的状态,即可把空间复杂度由O(n)降为O(1)。
方法四:贪心
贪心算法的策略为只要每天的价格比前一天的价格低,就买入前一天的并卖出,这样就可以获得最大收益。其实质和方法一相似,只是方法一是一大段一大段贪,这里是一天一天的贪。
代码
#include <vector>
#include <limits>
using namespace std;
class Solution {
public:
// 自己解法 拟人法
// - 一直维护最低价格、前一天的价格和总利润
// - 如果今天跌了,就意味着不再能卖出更高的价格,因此以昨天的价格卖出,
// 并且更新最低价格为今天的价格
// - 对于最后一天若没跌,则进行特殊处理,加上最后一天的利润
// 时间复杂度O(n),空间复杂度O(1)
int maxProfit1(vector<int>& prices) {
int n = prices.size();
if (n == 0) return 0;
int min_price{ prices[0] }, last_price{ prices[0] }, total_profit{ 0 };
for (int i = 1; i < n; ++i) {
// 最后一天且没跌
if (i == n - 1 && prices[i] >= last_price) { // 这里需要取到等号,因为会有类似输入为[1,2,2]的情况
total_profit += prices[i] - min_price;
}
// 今天跌了,则以昨天的价格卖出,并且更新最低价格为今天的价格
else if (prices[i] < last_price) {
total_profit += last_price - min_price;
min_price = prices[i];
}
// 更新最低价格
else if (prices[i] < min_price) {
min_price = prices[i];
}
// 更新前一天的价格
last_price = prices[i];
}
return total_profit;
}
// 动态规划
// 每天有三种选择:买/卖/不动;有两种状态:持仓/空仓。
// 由于不能同时参与多笔交易(必须在再次购买前出售掉之前的股票)
// 因此,若某一天持仓,则其下一天只能卖/不动,对应的下一天状态为空仓/持仓
// 若某一天空仓,则其下一天只能买/不动,对应的下一天状态为持仓/空仓
// 然后即可根据某天的状态写状态转移方程
// 时间复杂度O(n),空间复杂度O(n)
int maxProfit2(vector<int>& prices) {
int n = prices.size();
// 当前交易完后拥有的现金
// 第一维的n表示第n天
// 第二维的1/0表示当天最终手上有/无股票
vector<vector<int>> cur_cash(n, vector<int>(2));
if (n == 0) return 0;
cur_cash[0][0] = 0;
cur_cash[0][1] = -prices[0];
for (int i = 1; i < n; ++i) {
cur_cash[i][0] = max(cur_cash[i - 1][0], cur_cash[i - 1][1] + prices[i]); // 前一天空仓且不动/前一天持仓卖掉 -> 今天空仓
cur_cash[i][1] = max(cur_cash[i - 1][1], cur_cash[i - 1][0] - prices[i]); // 前一天持仓且不动/前一天空仓且买入 -> 今天持仓
}
return cur_cash[n - 1][0]; // 最后一天空仓才能现金最多
}
// 动态规划优化
// 因为状态转移方程只参考前一天的,因此只需要记录前一天的两个状态
// 时间复杂度O(n),空间复杂度O(1)
int maxProfit3(vector<int>& prices) {
int n = prices.size();
// 当前交易完后拥有的现金
// 第一维的n表示第n天
// 第二维的1/0表示当天最终手上有/无股票
if (n == 0) return 0;
int pre_cash_empty{ 0 }, pre_cash_hold{ -prices[0] }, cur_cash_empty{ 0 }, cur_cash_hold{ 0 };
for (int i = 1; i < n; ++i) {
cur_cash_empty = max(pre_cash_empty, pre_cash_hold + prices[i]); // 前一天空仓且不动/前一天持仓卖掉 -> 空仓
cur_cash_hold = max(pre_cash_hold, pre_cash_empty - prices[i]); // 前一天持仓且不动/前一天空仓且买入 -> 持仓
pre_cash_empty = cur_cash_empty;
pre_cash_hold = cur_cash_hold;
}
return cur_cash_empty; // 最后一天空仓才能现金最多
}
// 贪心,如果每天的价格比前一天的价格高,就卖出
// 此方法与拟人法的区别是拟人是一大段一大段卖,而此方法是一天一天卖
// 时间复杂度O(n),空间复杂度O(1)
int maxProfit(vector<int>& prices) {
int n = prices.size();
if (n == 0) return 0;
int pre_price{ prices[0] };
int total_profit{ 0 };
for (int price : prices) {
if (price > pre_price) total_profit += price - pre_price;
pre_price = price;
}
return total_profit;
}
};
int main() {
Solution s;
vector<int> input{ 1,2,3,4,5 };
int output;
output = s.maxProfit(input);
return output;
}
收获
- 之前学的拟人法在这用上了,而且复杂度也很优秀
- 相当于做了一道最简单二维动态规划题目,并且像上一道题目一样对其进行了优化(不过我做的时候并没有想到,有点可惜)
- 再次加强了对贪心算法的掌握