这是leetcode买卖股票系列的一个总结,难度由浅入深,参考了其他博文,在此罗列如下,特表感谢:
https://blog.csdn.net/u012501459/article/details/46514309
https://blog.csdn.net/linhuanmars/article/details/23236995
https://www.cnblogs.com/grandyang/p/4281975.html
http://bookshadow.com/weblog/2015/11/24/leetcode-best-time-to-buy-and-sell-stock-with-cooldown/
买卖股票的最佳时机
描述:
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。
注意你不能在买入股票前卖出股票。
示例 1:
输入: [7,1,5,3,6,4] 输出: 5 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2:
输入: [7,6,4,3,1] 输出: 0 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
思路:
由于只允许交易一次,则找到数组中最大的差值对输出就是答案,在答案中要注意小的数字必须在大的数字之前,否则不符合题意(低买高卖)。编程的思路是设定两个变量current(当前值卖出,即a[i]时卖出的利润),max(截止到a[i]时卖出的历史最大利润),当current<=0时,表示当前值卖出的当下利润为负数,则把current=0,current记录的是a[i]与小于i的之前某个局部最小值的差值(比如a[j],j必须小于i),如果current为正表示a[i]>a[j],如果小于等于表示a[i]<=a[j],则表明如果当前以a[i]买入,则有可能在后续扫描中获得更大利润,由于把current重新赋值为0的时候,max已经记录了截止到a[i]卖出的历史最大利润,则可以通过比较当下的max与i之后得到的current中取最大的值,便可以得到最大的差值。
举例如下:
输入: [7,1,5,3,6,4,0,10] 输出: 5 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
在下图中,i表示array的下标,current表示扫描到对应i下的值,max表示扫描到对应i下的值。我们以i=0开始扫描,由于a[1]-a[0]<0,所以把current归零,然后从i=1开始扫描,由于a[2]>a[1],所以i=2时的current=a[2]-a[1],同理i=3时current=a[3]-a[1]>0也满足题意,则更新max。。。一直到i=6,此时current=a[6]-a[1]<=0,所以把current归零,更新max。以此类推到数组结束,最后计算的max便是答案。
c++程序如下:
int maxProfit(vector<int>& prices) { if (prices.size() <= 1) { return 0; } int max = 0; int current = 0; int n = prices.size(); for (int i = 1; i < n; i++) { current += prices[i] - prices[i - 1]; if (current <= 0) { current = 0; continue; } if (max < current) { max = current; } } return max; }
买卖股票的最佳时机 II
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: [7,1,5,3,6,4] 输出: 7 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
示例 2:
输入: [1,2,3,4,5] 输出: 4 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。 因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入: [7,6,4,3,1] 输出: 0 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
思路:
这道题比上道题简单,由于可以尽可能多的交易,则只要当a[i]-a[i-1]为正时不断累加就可以,即可以“预知未来”,如果我知道明天股价会长,那我就今天买明天卖,否则今天就不买。
代码如下:
int maxProfit(vector<int>& prices) { if (prices.size() <= 1) { return 0; } int current = 0; int n = prices.size(); for (int i = 1; i < n; i++) { if (prices[i] - prices[i - 1] > 0) { current += prices[i] - prices[i - 1]; } } return current; }
买卖股票的最佳时机 III
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: [3,3,5,0,0,3,1,4] 输出: 6 解释: 在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。 随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。
示例 2:
输入: [1,2,3,4,5] 输出: 4 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。 因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入: [7,6,4,3,1] 输出: 0 解释: 在这个情况下, 没有交易完成, 所以最大利润为 0。
思路:
方法一:
这道题由于最多可以完成完成两次交易,那么可以通过第一种方法的思路做两次,定义两个数组former和latter,former[i]中存截止到a[i]时,数组小于等于i的数字部分能获得的最大利润,latter[i]中存储截止到a[i]时,数组大于等于i的数字部分能获得的最大利润。由于第一次扫描填充former,第二次扫描填充latter,我们还需要再扫描一次,把每次的former[i]+latter[i]的最大值取出来,便可以得到答案。
这种方法和第一种方法思路差不对,只不过第一次正着扫描数组填充former,第二次倒着扫描数组填充latter,第三次再扫描一次,总体时间复杂度为O(n),空间复杂度为O(n)。代码如下:
int maxProfit(vector<int>& prices) { if (prices.size() <= 1) { return 0; } int max = 0; int current = 0; int n = prices.size(); vector<int> former(n); vector<int> latter(n); former[0] = 0; for (int i = 1; i < n; i++) { current += prices[i] - prices[i - 1]; if (current <= 0) { current = 0; } if (max < current) { max = current; } former[i] = max; } max = 0; current = 0; latter[n - 1] = 0; int maxPrice = prices[n - 1]; for (int i = n - 2; i >= 0; i--) { current = maxPrice - prices[i]; if (current <= 0) { current = 0; maxPrice = prices[i]; } if (max < current) { max = current; } latter[i] = max; } int sum = 0; current = 0; for (int i = 0; i < n; i++) { current = 0; current = former[i] + latter[i]; if (current > sum) { sum = current; } } return sum; }
方法二:
第二种思想是用动态规划维护两个变量local局部最优和global全局最优,且二级递推公式如下:
local[i][j] = max(global[i - 1][j - 1] + max(diff, 0), local[i - 1][j] + diff) global[i][j] = max(local[i][j], global[i - 1][j])
解释如下:
diff表示差值即a[i]-a[i-1],local[i][j]表示第i天完成第j份交易并且第j份交易必须在第i天完成的局部最优解,于是local[i][j]由以下3中情况组成:
1. 今天刚买的
那么 Local(i, j) = Global(i-1, j-1)
相当于今天买今天卖,啥也没干
2. 昨天买的
那么 Local(i, j) = Global(i-1, j-1) + diff
等于Global(i-1, j-1) 中的交易,加上今天干的那一票
3. 更早之前买的
那么 Local(i, j) = Local(i-1, j) + diff
昨天别卖了,留到今天卖
所以:
local[i][j] = max(global[i - 1][j - 1] + max(diff, 0), local[i - 1][j] + diff)
对于全局最优,则取当下local[i][j]和所有j次交易都在i-1天完成的全局最优中取最大就行了
所以:
global[i][j] = max(local[i][j], global[i - 1][j])
c++代码如下(2维数组)
int maxProfit(vector<int> &prices) { if (prices.empty()) return 0; int n = prices.size(), g[n][3] = {0}, l[n][3] = {0}; for (int i = 1; i < prices.size(); ++i) { int diff = prices[i] - prices[i - 1]; for (int j = 1; j <= 2; ++j) { l[i][j] = max(g[i - 1][j - 1] + max(diff, 0), l[i - 1][j] + diff); g[i][j] = max(l[i][j], g[i - 1][j]); } } return g[n - 1][2]; }
优化为一维数组:
我们希望能在空间上优化成1维数组,减少空间开销,即我们希望只申请local[3]和global[3],这里申请3是因为更新local[1]需要用到local[0]的值,所以要申请k+1即2+1个。总体思想和上面一样,这里着重解释下为什么k的循环要从大到小,如下图所示是二维数组的更新图示:
不同颜色表示i循环,相同颜色的圆圈表示j循环,每次更新的流程如下图所示,先更新相同颜色的数组,在更新不同颜色的数组,相同颜色的数组中更新顺序为,如果j由小到大,则更新顺序为l[1][1],g[1][2] l[1][2],g[1][2],我们看到g是依赖相同i的l,而不依赖其他,存储成二维数组四个值是共存的,且往上一级蓝色数组更新的时候红色的数组的值是不会变的,所以不存在依赖的问题。下面我们来看如果只有一维数组会出现什么情况。
如果我们只有一维数组即local[3]和global[3],那么会存在数组覆盖的问题,如下图所示,此时我们的数组的形式如下:
当更新到蓝色的l[1]是,红色的l[1]会被重写,因为其实无论是红色还是蓝色,我们只定义了l[0],l[1]和l[2],在同一时刻只有一个l[1],数据的更新如下,根据递推公式,箭头代表有依赖关系(有些依赖关系喂画出),更新蓝色色块时,g[1]依赖g[1],当更新成功后会覆盖g[1],l[2]也依赖g[1],更新完后不会覆盖,所以如果先更新蓝色块的第一列,再更新第二列(j从1到2),会出现l[2]获取到更新了的g[1],而不是旧的g[1],所以我们需要j从2大到小,就不会出现覆盖的问题。
c++代码如下:
int maxProfit(vector<int>& prices) { int n = prices.size(); if (n <= 0) { return 0; } int local[3] = { 0 }; int global[3] = { 0 }; for (int i = 1; i < n; i++) { int diff = prices[i] - prices[i - 1]; for (int j = 2; j >= 1; j--) { local[j] = max(global[j-1]+max(diff,0),local[j]+diff); global[j] = max(local[j],global[j]); } } return global[2]; }
Best Time to Buy and Sell Stock with Cooldown
给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
- 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
- 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
示例:
prices = [1, 2, 3, 0, 2] maxProfit = 3 transactions = [buy, sell, cooldown, buy, sell]
思路:
这道题和前几道题不同之处在于有冷却时间,所以前几种方法都不可行,这里用一种新的思路,通过构造两个辅助数组sell和buy来解题,具体步骤如下:
方法一:
sells数组:sells[i]表示在第i天卖出股票后的利润(注意:这里不是最大利润,只考虑了在第i天卖出的利润,这点和方法二不一样)。
buys数组:buys[i]表示在第i天买入股票后的利润。
基于以上假设,我们可以写出下列递推式:
diff=prices[i]-prices[i-1]; sells[i] = max(buys[i-1]+prices[i],sells[i-1]+diff); buys[i] = max(sells[i-2]-prices[i],buys[i-1]-diff);
解释:diff表示差值,sells[i](第i天卖出股票的利润)等于昨天买入股票后的剩余利润+今天的股票价(昨买今卖)和昨天卖出股票的利润+差值(后悔昨天卖了,应该今天卖的)中的最大值。buys[i]等于sells[i-1](两天前卖了股票的利润)-今天股票的价格和昨天买股票的价格(buy[i-1])-差值(后悔昨天买股票,应该今天买的)。
最后遍历sells找最大值就行了。
在这里我们记住一点对于买或者卖而言利润是买负卖正。
在开始写程序之前,我们需要先进行初始化操作:
buys[0]=-prices[0]; sells[0]=0; buys[1]=-prices[1]; sells[1]=prices[1]-prices[0];
以下是c++程序:
int maxProfit(vector<int>& prices) { int n = prices.size(); if (n <= 1) { return 0; } int *sells = new int[n]; int *buys = new int[n]; memset(sells, 0, sizeof(int)*n); memset(buys, 0, sizeof(int)*n); sells[0] = 0; buys[0] = -prices[0]; sells[1] = max(0, prices[1] - prices[0]); buys[1] = -prices[1]; for (int i = 2; i < n; i++) { int diff = prices[i] - prices[i - 1]; sells[i] = max(buys[i-1]+prices[i],sells[i-1]+diff); buys[i] = max(sells[i-2]-prices[i],buys[i-1]-diff); } int max = 0; for (int i = 0; i < n; i++) { if (sells[i] > max) { max = sells[i]; } } return max; }
方法二:
方法二的核心思想和法一一样,需要两个辅助数组,但是对于辅助数组而言意义不一样。
sells数组表示在第i天卖出股票后的历史最大利润,而不是在第i天卖出的利润,所以最后返回sells[n-1]即是答案。
buys数组表示在第i天买出股票后的历史最大利润。
所以sells和buys的递推式如下:
diff=prices[i]-prices[i-1]; sells[i] = max(sells[i-1],buys[i-1]+prices[i]); buys[i] = max(buys[i-1],sells[i-2]-prices[i]);
我们可以看到(公式中加粗部分):
当前不管是sells还是buys都会在昨天的sells[i-1]或者buys[i-1]中考虑,即把上次的交易情况考虑进去了(即是局部最优和整体最优的思想),所以得出的每次都是最优解。最后返回sells[n-1]就是答案。具体分析过程和法一类似,这里不再赘述。
首先同样我们需要进行初始化:
buys[0]=-prices[0]; buys[1]=max(-prices[0],-prices[1]); //注意这里已经开始取最大值了 sells[0]=0; sells[1]=max(0,prices[1]-prices[0]);以下是c++代码:
int maxProfit(vector<int>& prices) { int n = prices.size(); if (n <= 1) { return 0; } int *sells = new int[n]; int *buys = new int[n]; memset(sells, 0, sizeof(int)*n); memset(buys, 0, sizeof(int)*n); sells[0] = 0; buys[0] = -prices[0]; sells[1] = max(0, prices[1] - prices[0]); buys[1] = max(-prices[0],-prices[1]); for (int i = 2; i < n; i++) { int diff = prices[i] - prices[i - 1]; sells[i] = max(sells[i-1],buys[i-1]+prices[i]); buys[i] = max(buys[i-1],sells[i-2]-prices[i]); } return sells[n-1]; }
Best Time to Buy and Sell Stock with Transaction Fee
给定一个整数数组 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
.
思路:
这道题不限制交易次数,所以我们还是沿用上道题构造两个辅助数组,并且递推公式如下:
sells[i]=max(sells[i-1],buy[i-1]+prices[i]-fee);
buys[i]=max(buys[i-1],sells[i-1]-prices[i]);
注意每次交易都要减去一个交易费用fee。
c++代码如下:
int maxProfit(vector<int>& prices, int fee) { int n = prices.size(); if (n <= 1) { return 0; } int sell =0; int buy =-prices[0]; //memset(sell, 0, sizeof(int)*n); //memset(buy, 0, sizeof(int)*n); //buy[0] = -prices[0]; //sell[0] = 0; int tmp_buy=buy; int tmp_sell=sell; for (int i = 1; i < n; i++) { buy = max(tmp_buy,tmp_sell-prices[i]); sell = max(tmp_sell,tmp_buy+prices[i]-fee); tmp_buy=buy; tmp_sell=sell; } return sell; }
这里为了减少空间复杂度,把需要O(n)辅助空间的数组降为O(1),时间复杂度为O(n)。
这里就是leetcode上暂时的股票买卖的问题,打卡整理,以后方便复习