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