LeetCode Best Time to Buy and Sell Stock买卖股票的最佳时机-系列

这是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 个元素是一支给定的股票在第 天的价格。

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

注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 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上暂时的股票买卖的问题,打卡整理,以后方便复习得意











猜你喜欢

转载自blog.csdn.net/qq_26410101/article/details/80309254
今日推荐