[leetcode] Best Time to Buy and Sell Stock IV

来源:互联网 发布:淘宝网儿童电动车 编辑:程序博客网 时间:2024/06/06 19:04

Problem Description:

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).

题目大意是,已知n天的股票价格,现在你可以对股票进行至多k组的买卖操作,求出最大收益。这里每一组操作为在第i天买进和在第j天卖出,这样一次操作的收益为prices[j] - prices[i],与天数无关。

这是一个求最大的问题,可以尝试动态规划算法。首先定义子问题,看看它是否具有重叠子问题和最优子结构两个性质,如果满足就可以写出动态规划的递推式了。

那么,如何定义子问题来对原始问题进行划分呢?

如果我们用dp[i][j]表示从第0 (表示下标) 天到第i天最多买卖j次获得的最大收益。那么可以这样推断dp[i][j]:

dp[i][j] = max {dp[i-1][j], dp[t][j-1]+p[i]-p[t+1]}, t in [0, i-2]

初始化dp[0][j]和dp[i][0]为0,然后递推下去得到dp[n-1][k]即为这n天里最多买卖k次的最大收益。 但是。。。


但是,这样做会超时。我们来分析一下它的时间复杂度:

为了填充二维的dp矩阵,我们需要对i (0,n)和j (0, k)进行循环,递推过程中还需要枚举t (0, i-2)的位置,因此时间复杂度为O(kn^2)。


下面我们就要想办法看能不能对上述方法进行优化,降低时间复杂度。

当j一定时,上述的方法先枚举第一次交易的卖出位置(i),然后考虑在该位置上是否交易;若交易则再枚举第一次交易的买进位置(t),递推下去;若不交易则直接在i-1位置递推下去。当j再循环增加时,我们按照保存的之前计算出来的局部最优值来更新再加一次交易的话,能够获取的最大利润。

(1)我们现在先把问题简化成只考虑一次交易,那么我们就要枚举这次交易的发生的起止位置,上述方法是O(n^2),可以参考“Best Time to Buy and Sell Stock”系列的其他题目,当限制为一次交易时,是可以在O(n)时间内完成的。

我们假设这次买进是在第一天prices[0],那么如果第二天卖出,收益就是prices[1]-prices[0],第三天卖出就是prices[2]-prices[0],...,第n天卖出就是prices[n-1]-prices[0]。然后我们再假设第二天买进,去枚举卖出时间,依次类推。等等,好像不用这么麻烦,我们直接找到数组中的最大最小值求差呢?这样做只有在最小值在最大值的前面时才有效,否则算出来的就是最大损失了。那么如何保证最小值在最大值的前面呢?反正我们要遍历一遍数组,在遍历的同时直接记录下前面所有元素的最小值,那么在第i天卖出时以前i天的最小值作为买入时间就可以最大化在这天卖出的最大利润了。这样我们就可以得到前i天里进行这次交易的最大收益maxprofit[i],全局最大值就是maxproft[n-1]。

(2)如果最多可以进行两次交易,在第一次交易的基础上,如何去进行第二次交易呢?

通过第一次的遍历,我们得到假设在前i天完成第一次交易的局部最大收益(不妨假设为preMax[i]=maxprofit[i]),如果所有的preMax[i]都是0的话,我们是不是回到了(1)也就是可以在O(n)时间内完成。但是如果他们都是非负数(肯定非负,因为如果任何交易都造成损失的话就不交易好了)呢?与(1)一样,我们还是在遍历i的时候,假设第二次交易的卖出发生第i天,那么为了收益更大,我们当然还是希望它是在前i天的最小值位置(不妨设为x)买进的。但是问题来了,x肯定是第一次交易的买进位置(方法一样,前i天的最小值不会变)啊,这一天是不允许有多次操作同时发生的,所以这两次交易相当于一次交易。另外,我们考虑另一种情况,虽然第y(<i)天的价格略高于第x天的,但是在第y天之前进行一次交易的最大收益是preMax[y-1],那么这两次交易的收益相当于是p[i] - p[y] + preMax[y-1] = p[i] - (p[y] - preMax[y-1])。看出端倪了吗?我们可以找在前i天中p[y]-preMax[y-1]的最小值,这样用p[i]减去它就得到了在第i天第二次卖出的局部最大收益。

(3)第三次,第四次,...,第k次的交易可以从前k-1次交易得到的局部最优值来推算。而且每次推算的过程是O(n)的。

所以这样做就可以在O(kn)下找到在n天里至多发生k次交易的最大利润。


另外,关于leetcode上的这道题,即使用上述优化后的算法依然会出现“Time Limit Exceeded”的错误。这个例子中k=10 billion,n=10000(也可能是只显示前1万个)。总之是k太大,然而并没有什么用,因为根本就不可能发生那么多次的交易。n天里最多可以发生n/2组买卖交易,所以当k大于或等于n/2时,可以直接遍历数组,把相邻两天的差值为正的利润加起来即为总的最大利润。我起初也很疑惑,画个图就可以明白了。把每天的股票价格画出来,我们可以看到一些上上下下的折线,现在我们可以进行任意次的买卖交易,那么最大利润当然是在所有的波谷买进,波峰卖出。

我们来看一个简单的例子:

[2, 1, 4, 6, 3, 5, 7, 2], k=4

最大利润 = (6-1) + (7-3)= 9

上述方法得到的利润:0 (1-2<0) + 3 (4-1) + 2 (6-4) + 0 (3-6<0) + 2 (5-3) + 2 (7-5) + 0 (2-7>0) = 9 (注意这里不代表每天进行买卖,如果要根据这个序列0,3,2,0,2,2,0来求买卖时间的话,直接找连续的正数段即可)

效果是一样的,因为它也是只考虑了所有折线中上升的部分,不同的是它可能把有些连续的折线拆成了多段。


0 0
原创粉丝点击