题目1----121. 买卖股票的最佳时机I:

链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。

解答:

限制只能买入卖出一次

DP1:

buy[i]记录截止到第i天是买入的状态的最小花费(值为负数)

 class Solution {
public:
int maxProfit(vector<int>& prices) {
if(prices.size()<2){
return 0;
}
int n=prices.size(),res=0;
vector<int> buy(n,0);
buy[0]=-prices[0];
for(int i=1;i<n;++i){
res=max(buy[i-1]+prices[i],res);
buy[i]=max(-prices[i],buy[i-1]);
}
return res;
}
};

严格来说这个不算dp,计算第i天的情况时,只用到了buy[i-1]的数据。。所以前面保存的数据是没有意义的。

DP2:

dp[i]记录第i天当天卖出的最大利润,则最大利润一定等于之前某天买今天卖。

i):首先可以昨天买今天卖。

ii):还可以之前某天买今天卖。dp[i-1]等于昨天之前买入,i-1天卖出的最大利润,那么i-1天不卖,改为第i天卖也可以得到一个利润。

两个利润取最大。

 class Solution {
public:
int maxProfit(vector<int>& prices) {
if(prices.size()<2){
return 0;
}
int n=prices.size();
vector<int> dp(n,0);
dp[0]=0;
int res=0;
for(int i=1;i<n;++i){
dp[i]=max(0,prices[i]-prices[i-1]);
dp[i]=max(dp[i],dp[i-1]-prices[i-1]+prices[i]);
res=max(res,dp[i]);
}
return res;
}
};

遍历:

和DP一样都是O(N)时间,和第一种dp一样想法,只是不用记录dp数组了。毕竟考察i的时候,只需要buy[i-1]的数据。
思路:最高利润出现在:买入为价格最低时,卖出为买入之后价格最高时。
故当更新最小值价格时,之前的最大价格要舍弃,从当前索引继续考察。

 class Solution:
def maxProfit(self, prices: List[int]) -> int:
l=len(prices)
if l<2:
return 0
_min,_max=0,0
i=0
res=0
while i<l:
if prices[i]<prices[_min]:
_min=i
_max=i
if prices[i]>prices[_max]:
_max=i
res=max(res,prices[_max]-prices[_min])
i+=1
return res

题目2----122. 买卖股票的最佳时机II:

链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

解答:

无限制次数的买入卖出

 class Solution {
public:
int maxProfit(vector<int>& prices) {
int n=prices.size();
if(n<){return ;}
vector<int> buy(n,),sell(n,);
buy[]=-prices[];
for(int i=;i<n;++i){
sell[i]=max(sell[i-],prices[i]+buy[i-]);//之前卖今天不卖 or 之前买今天卖
buy[i]=max(buy[i-],sell[i-]-prices[i]);//之前买今天不买 or 之前卖了今天买
}
return sell[n-];
}
};

题目3----123. 买卖股票的最佳时机III:

链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii/

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。

解答:

限制最多两次买入卖出

定义了4个dp数组,分别是

buy1[i]:第i天是第一次买入的状态

sell[i]:第i天是第一次卖出的状态

buy2、sell2同理。

由于一开始还不能第二次买和第二次卖,所以赋值为负无穷。

 class Solution {
public:
#define inf INT_MIN
int maxProfit(vector<int>& prices) {
int n=prices.size();
if(n<){return ;}
vector<int> buy1(n,),sell1(n,),buy2(n,inf),sell2(n,inf);
buy1[]=-prices[];
for(int i=;i<n;++i){
sell1[i]=max(prices[i]+buy1[i-],sell1[i-]);
buy1[i]=max(-prices[i],buy1[i-]);
sell2[i]=max(buy2[i-]+prices[i],sell2[i-]);
buy2[i]=max(buy2[i-],sell1[i-]-prices[i]);
}
return max(,max(sell2[n-],sell1[n-]));
}
};

方法2:先计算1次买卖最大的利润sell1[i]。再计算从第i天开始再买卖一次最大的利润sell2[i]。

第一次买卖是从前向后求(因为左侧是固定的),第二次买卖是从后向前求(因为右侧是固定的)

 class Solution {
public:
#define inf INT_MIN
int maxProfit(vector<int>& prices) {
int n=prices.size();
if(n<){return ;}
vector<int> sell1(n,),sell2(n,);
int min_buy=-prices[];
for(int i=;i<n;++i){
sell1[i]=max(sell1[i-],prices[i]+min_buy);
min_buy=max(min_buy,-prices[i]);
}
int res=max(,sell1[n-]);
int max_sell=prices[n-];
for(int i=n-;i>=;--i){
sell2[i]=max(sell2[i+],max_sell-prices[i]);
max_sell=max(max_sell,prices[i]);
res=max(res,sell1[i]+sell2[i]);
}
return res;
}
};

之前写过的python版本,以供参考:

 class Solution:
def maxProfit(self, prices) -> int:
l=len(prices)
if l<2:
return 0
res=0
#先算一个dp1数组
#dp1[i]表示截止到第i-1天只进行一次买卖的最大利润
dp1=[0 for i in range(l)]
max_price,min_price=prices[0],prices[0]
for i in range(1,l):
dp1[i]=max(dp1[i-1],prices[i]-min_price)
#对于第i天来说,1.如果当天卖:最大利润即当前卖出价格
#减去之前的最小买入价格,2如果不卖:最大利润和前一天的
#最大利润相同
min_price=min(min_price,prices[i]) #更新当前最小买入价格
max_price=max(max_price,prices[i]) #更新当前最大卖出价格
#对于任意k,dp1[k]表示k卖出的最大利润,
#那么需要求剩下k+1到n-1的最大利润
#倒着求,因为右边界不变始终为l-1,左边界在变化
#dp2[i]表示从i开始到最后只进行一次买卖的最大利润
res=dp1[-1]
# print(res)
dp2=[0 for i in range(l)]
max_price=prices[-1]
for i in range(l-2,-1,-1):
dp2[i]=max(dp2[i+1],max_price-prices[i])
#对于第i天,1.若当天买,则最大利润即之后的最大卖出价格减去
#当前买入价格,2.若当天不买,最大利润和后一天的最大利润相同
max_price=max(max_price,prices[i]) #更新当前最大卖出价格
res=max(res,dp1[i-1]+dp2[i]) if i>=1 else max(res,dp2[i])
# print(dp1)
# print(dp2)
return res

后来发现其实不需要存储dp数组,只需要一个变量记录上一次的状态就行,但我懒,就不写了,反正内存大任性

题目4----188. 买卖股票的最佳时机IV:

链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv/

给定一个数组,它的第 i 个元素是一支给定的股票在第 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。

这道题限制买卖次数最大为k,是一个变量。

设二维dp数组。dp[i][j]表示截止到i最多完成j笔交易的最大利润。

截止第i天,最大利润首先可以是之前的最大利润,即之前就完成了j笔交易,第i天不卖。

当然第i天也可以卖,那么本拨交易的买入最晚也得是i-1天,那么i-2天之前(包含i-2天)就必须要完成j-1笔交易。

用一个max值表示截止到前一天完成j-1笔交易、并且是待卖出状态的最大利润。

递推方程为:dp[i][j]=max(dp[i-1][j],max+prices[i])

另外每次循环中要更新max的值。

另外有用例k给的无限大,那么申请dp数组时会爆栈。需要判断一下k和价格数量的关系,如果k太大,转化为上面第2题的无限次的买卖股票问题。

前面的问题1和问题3只是这道题的特殊情况,代码直接复制过去就可以运行。

 class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
if(prices.empty()){
return ;
}
int n=prices.size();
if(*k<n){
vector<vector<int>> dp(n,vector<int>(k+,));
//dp[i][j]表示截止第i天完成最多j次交易得到的最大收益
for(int j=;j<k+;++j){
int _max=-prices[];
for(int i=;i<n;++i){
dp[i][j]=max(dp[i-][j],_max+prices[i]);
_max=max(dp[i-][j-]-prices[i],_max);
}
}
return dp.back().back();
}
else{//k太大,相当于可以无限次交易
vector<int> dp(n,);
//dp[i]表示截止第i+1天得到的最大收益
int _max=-prices[];
for(int i=;i<n;++i){
dp[i]=max(dp[i-],_max+prices[i]);
_max=max(_max,dp[i]-prices[i]);
}
return dp.back();
}
return ;
}
};

题目5----309. 最佳买卖股票时机含冷冻期:

链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/

给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。​

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

这道题多了一个冷冻期的限制:卖出之后要至少休息一天才能买入

那么理所当然的,多设立一个dp数组以表示冷冻期的状态:

 class Solution {
public:
int maxProfit(vector<int>& prices) {
if(prices.size()<){
return ;
}
int n=prices.size();
vector<int> buy(n,),sell(n,),freeze(n,);
//buy[i]、sell[i]、freeze[i]分别表示第i天
//买入、卖出、不买不卖的最大收益(包含买股票花的钱)
buy[]=-prices[];
int res=;
for(int i=;i<n;++i){
buy[i]=max(buy[i-],freeze[i-]-prices[i]);
sell[i]=max(sell[i-],buy[i-]+prices[i]);
freeze[i]=max(sell[i-],freeze[i-]);
res=max(sell[i],res);
}
return res;
}
};

也可以仿照第四题的解法,用一个max值保存截止前一天待卖出状态的最大利润,不过更新max的时候,要注意冷冻期的要求。所以_max=max(_max,dp[i-]-prices[i]),即截止i-2天卖出的最大利润,休息一天,第i天买入。之前的题目是_max=max(_max,dp[i-]-prices[i]),注意二者的区别,虽然只是1和2的数字不同,但却是整道题的关键

 class Solution {
public:
int maxProfit(vector<int>& prices) {
int n=prices.size();
if(n<){return ;}
vector<int> sell(n,);
sell[]=max(,prices[]-prices[]);
int _max=max(-prices[],-prices[]);
for(int i=;i<n;++i){
sell[i]=max(sell[i-],_max+prices[i]);
_max=max(_max,sell[i-]-prices[i]);
}
// for(int x:sell){cout<<x<<" ";};
return sell[n-];
}
};

题目6----901. 股票价格跨度:

链接:https://leetcode-cn.com/problems/online-stock-span/

编写一个 StockSpanner 类,它收集某些股票的每日报价,并返回该股票当日价格的跨度。

今天股票价格的跨度被定义为股票价格小于或等于今天价格的最大连续日数(从今天开始往回数,包括今天)。

例如,如果未来7天股票的价格是 [100, 80, 60, 70, 60, 75, 85],那么股票跨度将是 [1, 1, 1, 2, 1, 4, 6]。

其实题目就是找今天之前最近的比今天价格大的一天,如果是从昨天顺序往前查找,时间复杂度太高。下面有两种方法可以优化时间效率。

方法1:DP,有最大、最小、最多这种字眼的一般都是DP,极个别可能是DFS。

用一个一维数组dp,dp[i]表示第i天的股票价格跨度。如果i-1天的价格小于等于第i天,那么dp[i]可以直接加上dp[i-1]的值,这是因为dp[i-1]是小于等于i-1天价格的连续天数,而且i-1天价格还不大于第i天。

拿题目中的数组举栗子,当前第5天,之前0~4天的价格:100, 80, 60, 70, 60;已经算好的dp数组:1,1,1,2,1。

那么第5天价格75,i-1=4,第4天价格60,dp[4]=1,dp[i]可以加1。下面考虑4-1=3天

此时考虑第3天的价格为70,依然小于等于75,所以dp[i]再加dp[3]=2。下面考虑3-2=1天

再考虑第1天,发现其价格80>75,则退出。

概括点说,就是利用空间换取时间,保存了每一段跳跃的天数,求某一天的结果时,不再顺序查找,而是利用之前保存的数据,每次跳一大步直到目的地。平均来说,跳跃的步数应该为1。

时间O(N),空间O(N)

代码:

 vector<int> dp;
vector<int> prices;
class StockSpanner {
public:
StockSpanner() {
dp.clear(),prices.clear();
prices.push_back(INT_MAX); //设置第-1天价格无穷大,这样避免之后遍历中单独判断
dp.push_back();
} int next(int price) {
prices.push_back(price);
dp.push_back();//自己大于等于自己
int ori=dp.size()-,i=ori;
while(prices[i-]<=prices[ori]){//每次跳一段
dp[ori]+=dp[i-];
i-=dp[i-];
}
return dp.back();
}
}; /**
* Your StockSpanner object will be instantiated and called as such:
* StockSpanner* obj = new StockSpanner();
* int param_1 = obj->next(price);
*/

方法2:单调栈,这个是看题解来的,记录在一起。

其实之前也做过单调栈的题目,好像也是在leetcode上的,什么天际线的。但碍于相关题目不多,没有系统学过,做题时想不起来这个方法。

原理是这个题对于今天如果价格是100,那么今天以前的所有价格小于等于100的天的数据就不需要存储了,即数据结构中之前存储的比当前数更小的数就移除。这个性质正好符合递减栈,如果栈顶小于等于当前要push的数字,就把栈顶pop掉。直到栈顶大于当前要push的数字,再push。

还是用题目的例子:如果未来7天股票的价格是 [100, 80, 60, 70, 60, 75, 85],那么股票跨度将是 [1, 1, 1, 2, 1, 4, 6]

我们用两个同步栈(即同时push,同时pop)来做。当然设置一个新struct里面包含价格和价格跨度两个变量也可以用一个栈。。

价格栈prices,跨度栈(即保存结果的栈)kuadu。

最开始prices push无穷大,kuadu  push1,这样依然可以保持递减栈性质,不影响结果,为了省去后面判断栈空的代码。。(你不push也可以)

1.  100<无穷大(下面用inf表示),push

两个栈:inf ,100        1,1

2.  80<100,push

两个栈:inf,100,80      1,1,1

3.  60<80 ,push

两个栈:inf,100,80,60      1,1,1,1

4.  70>60 ,当前跨度初始值tmp为1,因为自己也小于等于自己。然后tmp+=跨度栈的栈顶(1),1+1=2

  再pop两个栈顶

两个栈:inf,100,80        1,1,1

  70<80,push:价格栈push70,跨度栈push  tmp

两个栈:inf,100,80,70      1,1,1,2

之后以此类推。其实只要自己纸上画一画,还是很好想明白的。

时间O(N),空间O(N)

代码:

 class StockSpanner {
public:
stack<int> prices;//递减栈
stack<int> kuadu;
StockSpanner() {
prices.push(INT_MAX),kuadu.push();
} int next(int price) {
int tmp=;
while(prices.top()<=price){
tmp+=kuadu.top();
prices.pop(),kuadu.pop();
}
prices.push(price);
kuadu.push(tmp);
return tmp;
}
}; /**
* Your StockSpanner object will be instantiated and called as such:
* StockSpanner* obj = new StockSpanner();
* int param_1 = obj->next(price);
*/

题目7----714. 买卖股票的最佳时机加手续费:

给定一个整数数组 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.

注意:

  • 0 < prices.length <= 50000.
  • 0 < prices[i] < 50000.
  • 0 <= fee < 50000.

我开始想复杂了,用了二维dp做的,代码应该是对的,但是后面的用例爆栈了。

后来看了下别人的解法,一维dp即可解决。和第二题无限次交易股票的题目相比,只是在买的时候或者卖的时候减去一个fee即可(由你自己决定)。

代码:

其中have_stock是表示当前持有股票,no_stock是当前不持有股票

 class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
if(prices.size()<){return ;}
int n=prices.size();
vector<int> have_stock(n,),no_stock(n,);
have_stock[]=-prices[];
for(int i=;i<n;++i){
have_stock[i]=max(have_stock[i-],no_stock[i-]-prices[i]);
no_stock[i]=max(no_stock[i-],have_stock[i-]+prices[i]-fee);
}
return no_stock[n-];
}
};

Leetocode7道买卖股票问题总结(121+122+123+188+309+901+714)的更多相关文章

  1. 领扣-121/122/123/188 最佳买卖时机 Best Time to Buy and Sell MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  2. 121. 122. 123. 188. Best Time to Buy and Sell Stock *HARD* 309. Best Time to Buy and Sell Stock with Cooldown -- 买卖股票

    121. Say you have an array for which the ith element is the price of a given stock on day i. If you ...

  3. LeetCode No.121,122,123

    No.121 MaxProfit 买卖股票的最佳时机 题目 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格. 如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你 ...

  4. leetcode 121 122 123 . Best Time to Buy and Sell Stock

    121题目描述: 解题:记录浏览过的天中最低的价格,并不断更新可能的最大收益,只允许买卖一次的动态规划思想. class Solution { public: int maxProfit(vector ...

  5. [LeetCode] 121. Best Time to Buy and Sell Stock 买卖股票的最佳时间

    Say you have an array for which the ith element is the price of a given stock on day i. If you were ...

  6. [LeetCode] 122. Best Time to Buy and Sell Stock II 买卖股票的最佳时间 II

    Say you have an array for which the ith element is the price of a given stock on day i. Design an al ...

  7. [LeetCode] 123. Best Time to Buy and Sell Stock III 买卖股票的最佳时间 III

    Say you have an array for which the ith element is the price of a given stock on day i. Design an al ...

  8. [LeetCode] 309. Best Time to Buy and Sell Stock with Cooldown 买卖股票的最佳时间有冷却期

    Say you have an array for which the ith element is the price of a given stock on day i. Design an al ...

  9. Leetcode之动态规划(DP)专题-121. 买卖股票的最佳时机(Best Time to Buy and Sell Stock)

    Leetcode之动态规划(DP)专题-121. 买卖股票的最佳时机(Best Time to Buy and Sell Stock) 股票问题: 121. 买卖股票的最佳时机 122. 买卖股票的最 ...

随机推荐

  1. PHP 安装扩展步骤

    一般来说php安装扩展需要几下几个步骤   1.下载扩展包    比如  pdo_mysql.tar.gz  (如果不想下载,可以到php安装目录,(类似php-5.3.3/ext/)的ext文件中找 ...

  2. 【5min+】后台任务的积木。.NetCore中的IHostedService

    系列介绍 [五分钟的dotnet]是一个利用您的碎片化时间来学习和丰富.net知识的博文系列.它所包含了.net体系中可能会涉及到的方方面面,比如C#的小细节,AspnetCore,微服务中的.net ...

  3. .Net core webapi使用httpClient发送异步请求遇到TaskCanceledException: A task was canceled

    前言:本人最近较多使用.net core的项目,最近在使用httpClient发送请求的时候,遇到服务器处理时间较长时,就老是会报异常:TaskCanceledException: A task wa ...

  4. VSTO开发指南(VB2013版) 第三章 Excel编程

    通过前两章的内容,有了一定的基础,但进入第三章,实例的步骤非常多,并且随着VS版本的升级,部分功能菜单界面发生了很大变化,所以,第三章的案例我将逐步编写! 实例3.1的目标就是给Excel写一个加载宏 ...

  5. 5.Python安装依赖(包)模块方法介绍

    1.前提条件 1). 确保已经安装需要的Python版本 2). 确保已经将Python的目录加入到环境变量中 2. Python安装包的几种常用方式 1). pip安装方式(正常在线安装) 2). ...

  6. MySQL 的一条语句是怎么执行的

    该文为< MySQL 实战 45 讲>的学习笔记,感谢查看,如有错误,欢迎指正 一.MySQL 的基础架构 以下就是 MySQL 的基础架构图. 在 Linux 中安装 MySQL 时,最 ...

  7. git 流程

    1.git clone 拉取代码2.git checkout -b '分支名称' 命令意思: 创建并切换到当前新建的本地分支. 查看并创建分支,先远端,后本地.3.将本地分支和远端分之关联 git b ...

  8. Spark调优指南

    Spark相关问题 Spark比MR快的原因? 1) Spark的计算结果可以放入内存,支持基于内存的迭代,MR不支持. 2) Spark有DAG有向无环图,可以实现pipeline的计算模式. 3) ...

  9. tomcat - 解决 org.bouncycastle.asn1.ASN1Boolean 非法循环依赖的错误

    背景 记录遇到一次奇怪的错误,在发布war包到Tomcat的时候,出现了org.bouncycastle.asn1.ASN1Boolean非法循环依赖的错误. INFO: Deploying web ...

  10. BZOJ #2506. calc [根号分治,莫队,二分]

    \(p\) 是个正常范围, \(\sqrt p <= 100\) 比较小,预处理出来 \(a_i % p == k\) 的位置,然后丢进去,最后询问的 \(p\) 如果大于 \(100\) 就莫 ...