leetcode 第188题,我的解法,Best Time to Buy and Sell Stock IV

<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">leetcode第188题,Best Time to Buy and Sell Stock IV题目如下:</span>

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

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



Design an algorithm to find the maximum profit. You may complete at most k transactions.


Note:

You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).


int maxProfit(int k, int prices[], int n)

对题目的分析

股票有涨有落,题目要求在已知未来股票走势情况下,对一支股票进行买卖,取得最大的收入,而且要求用户在卖股票之前必须先买这支股票。
转化成数学问题就是有一个数组,求得最多2k个点,b1,b2,...,bk. s1,s2,...,sk, 其中bi表示买入的时间点,si表示卖出的时间点。满足0<=b1<s1<b2<s2<b3<s3<...<bk<sk<n。

思路分析

此问题类似动态规划问题,对于给的数组,f(k,n)不小于f(n,k-1),当f(k,n)=f(k-1,n)时,认为对这一数组进行操作不会增加最后的数值。设数组为p[n]

对于已知的b1,b2....b(k-1),s1,s2,..s(k-1),如何得到下一组买入时间和卖出时间呢?对于最终的a(k),b(k),一定存在某个i,使得s(i)<B<S<b(i+1),或者b(i)<S<B<s(i)。
  • 第一种情况:s(i)<B<S<b(i+1)  这种情况下,在s(i)时刻,股票仍然在市场上,在B时刻,买入股票,在S时刻卖出股票。因此B和S一定是在(s(i),b(i+1))中满足B<S且p[S]-p[B]最大的两个下标。
  • 第二种情况:b(i)<S<B<s(i)   这种情况下,在b(i)时刻,股票在用户手中,用户在S时刻卖出股票,在B时刻买入,再次在S时刻卖出。因此B和S一定是在(b(i),s(i))中满足p[S]-p[B]最大且B>S的两个下标。
每一次增加k时,每一个i<k都有两个以上的<B,S>二元组,那么应该选取哪个i呢?
k次买卖后,整个数组分为了最多2k+1段,在(b(i),s(i))中,股票在用户手中,其他段,股票在市场上。在每一段上,用户能继续获得的利润的计算方式不同。如果我们计算每一段用户能获得的最大利润,继续操作时,我们从所有段中选择继续获得利润最大的一段。在这一段上进行买入卖出或卖出买入,即为下一操作能获得的最大利润。注意到每一次操作都会将1段拆为3段。
如下图所示:


我们使用两个最大堆,第一个堆存放在市场上的时间段,其中每个元素包含最大的用户可以获得的最大利润。第二个堆存放在用户手中的时间段,其中每个元素包含用户可以增长的最大利润。在每一次操作中,我们分别从两个堆中去的最大值,比较两者,选择较大值H,将最大利润增加H,然后维护两个堆。即删除一个堆中的最大值,然后向两个堆中插入总共3个元素。
当操作k次或者两个堆中最大元素为0或两个堆均为空时,即可宣告结束。

数据结构设计

设计两个堆,两个堆均用数组表示,以节省内存空间。由于k的值为变量,且可能很大。为了节省内存开支,因此首先申请长度50的数组来存储堆,当数组满时,将数组扩增到原来两倍大小。两个堆的元素如下:
struct buysells//股票在用户手中的时间段
{
	//股票在beginDay最低,在endDay最高,increaseProfit = p(sellDay)-p(buyDay)
	//beginDay<sellDay<buyDay<endDay
	int beginDay;//用户在beginDay买入
	int endDay;//用户在beginday卖出
	int buyDay;//若想获得最大利润增长,需再次在buyDay买入
	int sellDay;//若想获得最大利润增长,需再次在sellDay卖出
	int increaseProfit;//最大的利润增长价值
};
struct freetimes//股票在市场上的时间段
{
	//startDay<=buyDay<sellDay<=endDay
	//profit = p(sellDay)-p(buyDay)
	int startDay;//股票在市场上第一天
	int endDay;//股票在市场上最后一天
	int buyDay;//若想获得最大利润,需在buyDay买入
	int sellDay;//若想蝴蝶最大利润,需在sellDay卖出
	int profit;//用户可以获得最大利润
};

代码

struct buysells//股票在用户手中的时间段
{
	//股票在beginDay最低,在endDay最高,increaseProfit = p(sellDay)-p(buyDay)
	//beginDay<sellDay<buyDay<endDay
	int beginDay;//用户在beginDay买入
	int endDay;//用户在beginday卖出
	int buyDay;//若想获得最大利润增长,需再次在buyDay买入
	int sellDay;//若想获得最大利润增长,需再次在sellDay卖出
	int increaseProfit;//最大的利润增长价值
};
typedef struct buysells buysell;
typedef buysell *buysellpointer;

struct freetimes//股票在市场上的时间段
{
	//startDay<=buyDay<sellDay<=endDay
	//profit = p(sellDay)-p(buyDay)
	int startDay;//股票在市场上第一天
	int endDay;//股票在市场上最后一天
	int buyDay;//若想获得最大利润,需在buyDay买入
	int sellDay;//若想蝴蝶最大利润,需在sellDay卖出
	int profit;//用户可以获得最大利润
};
typedef struct freetimes freetime;
typedef freetime * freetimepointer;

//扩展数组到原来的两倍,其中*bsppNumLimit为原来的数组最大长度
//由于数组中存的是buysellpointer,因此需要指向指针的指针做为参数,buysellpointer **
void expandBSPHeap(buysellpointer **heap, int *bsppNumLimit)
{
	//分配两倍的数组空间,每个数组中存放的是buysellpointer
	buysellpointer* newheap = (buysellpointer*)malloc(*bsppNumLimit *2 * sizeof(buysellpointer));

	for (int i = 0; i < *bsppNumLimit; i++)
	{
		newheap[i] = (*heap)[i];
	}
	free(*heap);//释放原有的buysellpointer数组
	*heap = newheap;//由于需要改变heap,因此使用指针
	*bsppNumLimit = *bsppNumLimit * 2;
}


void insertBSP(buysellpointer BSP,buysellpointer **heap, int *length, int *bsppNumLimit)
{
	if(*length == *bsppNumLimit)
		expandBSPHeap(heap,bsppNumLimit);

	(*heap)[(*length)++] = BSP;
	int index = *length-1;
	buysellpointer tempBSP = BSP;
	//向堆中插入元素
	while (index!=0 && (*heap)[(index+1)/2-1]->increaseProfit < tempBSP->increaseProfit )
	{
		(*heap)[index] = (*heap)[(index+1)/2-1];
		index = (index+1)/2 -1;
	}
	(*heap)[index] = BSP;
}

void  deleteBSP(buysellpointer *heap, int *length)
{
	//删除最大元素
	if(*length == 0)
		return;
	(*length)--;
	free(heap[0]);//释放空间
	heap[0] = heap[*length];
	buysellpointer tempBSP = heap[*length];
	int index = 0;
	int maxChildIndex = 1;
	while (maxChildIndex < *length)
	{
		if(maxChildIndex + 1 < *length)
			if(heap[maxChildIndex+1]->increaseProfit > heap[maxChildIndex]->increaseProfit)
				maxChildIndex++;
		if(heap[maxChildIndex]->increaseProfit > tempBSP->increaseProfit)
		{
			heap[index] = heap[maxChildIndex];
			heap[maxChildIndex] =  tempBSP;
			index = maxChildIndex;
			maxChildIndex = (index+1)*2 - 1;
		}
		else
			break;
	}
}

void expandFTPHeap(freetimepointer **heap, int *fppNumLimit)
{
	freetimepointer* newheap = (freetimepointer*)malloc(*fppNumLimit *2 * sizeof(buysellpointer));

	for (int i = 0; i < *fppNumLimit; i++)
	{
		newheap[i] = (*heap)[i];
	}
	free(*heap);
	*heap = newheap;
	*fppNumLimit = *fppNumLimit * 2;
}

void insertFTP(freetimepointer FTP,freetimepointer **heap, int *length, int *fppNumLimit)
{
	if(*length == *fppNumLimit)
		expandFTPHeap(heap,fppNumLimit);

	(*heap)[(*length)++] = FTP;
	int index = *length-1;
	freetimepointer tempFTP = FTP;
	while (index!=0 && (*heap)[(index+1)/2-1]->profit < tempFTP->profit )
	{
		(*heap)[index] = (*heap)[(index+1)/2-1];
		index = (index+1)/2 -1;
	}
	(*heap)[index] = tempFTP;
}

void  deleteFTP(freetimepointer *heap, int *length)
{
	if(*length == 0)
		return;
	(*length)--;
	free(heap[0]);
	heap[0] = heap[*length];
	freetimepointer tempFTP = heap[*length];
	int index = 0;
	int maxChildIndex = 1;
	while (maxChildIndex < *length)
	{
		if(maxChildIndex + 1 < *length)
			if(heap[maxChildIndex+1]->profit > heap[maxChildIndex]->profit)
				maxChildIndex++;
		if(heap[maxChildIndex]->profit > tempFTP->profit)
		{
			heap[index] = heap[maxChildIndex];
			heap[maxChildIndex] =  tempFTP;
			index = maxChildIndex;
			maxChildIndex = (index+1)*2 - 1;
		}
		else
			break;
	}
}

//找到股票在市场期间能获得的最大利润
void findmaxprofit(int prices[],freetimepointer ftp)
{
	int minDay = ftp->startDay;
	ftp->buyDay = minDay;
	ftp->sellDay = minDay;
	ftp->profit = 0;
	for (int i = ftp->startDay+1; i <= ftp->endDay; i++)
	{
		minDay = (prices[minDay] > prices[i]) ? i : minDay;
		if (prices[i] - prices[minDay] > ftp->profit)
		{
			ftp->buyDay = minDay;
			ftp->sellDay = i;
			ftp->profit = prices[i] - prices[minDay];
		}
	}
}

//找到股票在用户手中期间能获得的最大利润增长
void findMaxIncreaseProfit(int prices[],buysellpointer bsp)
{
	int maxDay = bsp->beginDay;
	bsp->buyDay = maxDay;
	bsp->sellDay = maxDay;
	bsp->increaseProfit = 0;
	for (int i = bsp->beginDay+1; i < bsp->endDay; i++)
	{
		maxDay = (prices[i] > prices[maxDay]) ? i : maxDay;
		if (prices[maxDay] - prices[i] > bsp->increaseProfit)
		{
			bsp->buyDay = i;
			bsp->sellDay = maxDay;
			bsp->increaseProfit = prices[maxDay] - prices[i];
		}
	}

}


int maxProfit(int k, int prices[], int n) {

	int totalProfit=0;
	int fppNumLimit = 50;
	//股票在市场中的时间段组成的堆
	freetimepointer *fpp= (freetimepointer*)malloc(fppNumLimit*sizeof(freetimepointer));
	int fppNum = 0;
	int bsppNumLimit = 50;
	//股票在用户手中的时间的组成的堆
	buysellpointer *bspp = (buysellpointer*)malloc(bsppNumLimit*sizeof(freetimepointer));
	int bsppNum = 0;
	//初始化第一个股票在市场中的时间段
	fpp[0] = (freetimepointer)malloc(sizeof(freetime));
	fpp[0]->startDay = 0;
	fpp[0]->endDay = n-1;
	findmaxprofit(prices,fpp[0]);
	if(fpp[0]->profit == 0)
		return 0;
	fppNum++;

	for (int i = 0; i < k; i++)
	{
		int maxIncreaseProfit =0, maxProfit =  0;
		if(fppNum)
			maxProfit = fpp[0]->profit;
		if(bsppNum)
			maxIncreaseProfit = bspp[0]->increaseProfit;
		if(maxIncreaseProfit == 0 && maxProfit==0)//已经没有收入增长,堆为空或者没有增长空间。
			return totalProfit;
		if(maxIncreaseProfit > maxProfit)//用户持有的时间段进行卖出买入
		{
			buysellpointer tempBSP = bspp[0];
			buysellpointer tempBSP1 = NULL;
			if (tempBSP->sellDay -1 > tempBSP->beginDay +1)
			{
				tempBSP1 = (buysellpointer)malloc(sizeof(buysell));
				tempBSP1->beginDay = tempBSP->beginDay;
				tempBSP1->endDay = tempBSP->sellDay;
				findMaxIncreaseProfit(prices,tempBSP1);
				if(tempBSP->increaseProfit == 0)
					free(tempBSP1);
				else
					insertBSP(tempBSP1, &bspp, &bsppNum,&bsppNumLimit);
			}
			if(tempBSP->endDay -1 > tempBSP->buyDay+1)
			{
				tempBSP1 = (buysellpointer)malloc(sizeof(buysell));
				tempBSP1->beginDay = tempBSP->buyDay;
				tempBSP1->endDay = tempBSP->endDay;
				findMaxIncreaseProfit(prices,tempBSP1);
				if(tempBSP->increaseProfit == 0)
					free(tempBSP1);
				else
					insertBSP(tempBSP1, &bspp, &bsppNum,&bsppNumLimit);
			}
			if(tempBSP->buyDay -1 > tempBSP->sellDay +1)
			{
				freetimepointer tempFTP = (freetimepointer)malloc(sizeof(freetime));
				tempFTP->startDay = tempBSP->sellDay +1;
				tempFTP->endDay = tempBSP->buyDay - 1;
				findmaxprofit(prices, tempFTP);
				if(tempFTP->profit == 0)
					free(tempFTP);
				else
					insertFTP(tempFTP,&fpp,&fppNum,&fppNumLimit);
			}
			totalProfit += maxIncreaseProfit;
			deleteBSP(bspp,&bsppNum);
		}
		else
		{
			freetimepointer tempFTP =  fpp[0];
			int buyDay = tempFTP->buyDay;
			int sellDay = tempFTP->sellDay;
			freetimepointer tempFTP1 = NULL;
			if(buyDay > tempFTP->startDay + 1)
			{
				tempFTP1 = (freetimepointer)malloc(sizeof(freetime));
				tempFTP1->startDay = tempFTP->startDay;
				tempFTP1->endDay = buyDay -1;
				findmaxprofit(prices,tempFTP1);
				if(tempFTP1->profit == 0)
					free(tempFTP1);
				else
					insertFTP(tempFTP1,&fpp,&fppNum,&fppNumLimit);
			}
			if(tempFTP->endDay > tempFTP->sellDay + 1)
			{
				tempFTP1 = (freetimepointer)malloc(sizeof(freetime));
				tempFTP1->startDay = tempFTP->sellDay + 1;
				tempFTP1->endDay = tempFTP->endDay;
				findmaxprofit(prices,tempFTP1);
				if(tempFTP1->profit == 0)
					free(tempFTP1);
				else
					insertFTP(tempFTP1,&fpp,&fppNum,&fppNumLimit);
			}
			if (tempFTP->sellDay-1 > tempFTP->buyDay+1 )
			{
				buysellpointer tempBSP = (buysellpointer)malloc(sizeof(buysell));
				tempBSP->beginDay = tempFTP->buyDay;
				tempBSP->endDay = tempFTP->sellDay;
				findMaxIncreaseProfit(prices,tempBSP);
				if(tempBSP->increaseProfit == 0)
					free(tempBSP);
				else
					insertBSP(tempBSP,&bspp,&bsppNum,&bsppNumLimit);
			}
			totalProfit += maxProfit;
			deleteFTP(fpp,&fppNum);
		}
	}
	return totalProfit;
}

总结

这个题目花费了我一天多的时间,做为编程菜鸟的我还算可以,独立做出一道有一定难度的题目,还学会了不少知识。
  1. 不知道数组大小的情况下或在大小为变量的情况下,可以用malloc方法分配内存,如 buysellpointer* newheap = (buysellpointer*)malloc(*bsppNumLimit *2 * sizeof(buysellpointer));
  2. 加强了对二重指针的理解,在我的解法中,还遇到了三层指针,如buysellpointer **heap,对指针、内存的理解更加深入了。
  3. 数组大小未知,可以先分配一定内存,如果不够,分配两倍内存,进行拷贝,然后返回。
  4. 对最大堆的插入和删除的理解深入,毕竟自己写代码。
  5. 不足在于代码太长,特别是两个不同类型的堆,每种操作要写两遍代码,十分不方便。shagnwang

posted on 2015-03-31 21:32  花老🐯  阅读(405)  评论(0编辑  收藏  举报

导航