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

时间:2022-06-28 14:58:07

<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段。
如下图所示:
leetcode 第188题,我的解法,Best Time to Buy and Sell Stock IV
leetcode 第188题,我的解法,Best Time to Buy and Sell Stock IV
我们使用两个最大堆,第一个堆存放在市场上的时间段,其中每个元素包含最大的用户可以获得的最大利润。第二个堆存放在用户手中的时间段,其中每个元素包含用户可以增长的最大利润。在每一次操作中,我们分别从两个堆中去的最大值,比较两者,选择较大值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