188. Best Time to Buy and Sell Stock IV

来源:互联网 发布:花生壳 该域名被锁定 编辑:程序博客网 时间:2024/06/10 06:35

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

这题主要参考了leetcode discuss 区的高分解法。

方法一:
我们知道,当k足够大,这个array能产生的最大利润就是每一个单独的valley-peak对的利润总和。当k有限制,就会涉及到相邻valley-peak对的合并问题。假设相邻的两队分别为,[v1, p1]和[v2, p2]. 如果v2小于v1,那么肯定不会有合并产生,因为不管p1, p2什么样,都不会有产生更大的组合。当p2小于p1, 也没有合并的意义。所以这里有一很聪明的做法,就是在处理这种合并问题上,把多个单独的区间的和看成一个总的区间和几个补余。例如,当v2大于v1, p2大于p1, 我们可以将p1-v1+p2-v2存成p2-v1加上p1-v2。

class Solution {public:    // We can find all adjacent valley/peak pairs and calculate the profits easily.     // Instead of accumulating all these profits like Buy&Sell Stock II, we need     // the highest k ones.    //     // The key point is when there are two v/p pairs (v1, p1) and (v2, p2), satisfying     // v1 <= v2 and p1 <= p2, we can either make one transaction at [v1, p2], or make     // two at both [v1, p1] and [v2, p2]. The trick is to treat [v1, p2] as the first     // transaction, and [v2, p1] as the second. Then we can guarantee the right max     // profits in both situations, p2 - v1 for one transaction and p1 - v1 + p2 - v2     // for two.    //     // Finding all v/p pairs and calculating the profits takes O(n) since there are     // up to n/2 such pairs. And extracting k maximums from the heap consumes another O(k*log(n)).    int maxProfit(int k, vector<int> &prices)     {        int ret = 0;        int n = prices.size();         int v = 0;  // valley index         int p = 0;  // peak index + 1;        vector<int> profits;        stack<pair<int, int>> vp_pairs;        while (p < n)         {            // Find next valley/peak pair.            for (v = p; (v < n - 1) && (prices[v] >= prices[v + 1]); v++);            for (p = v + 1; (p < n) && (prices[p] >= prices[p - 1]); p++);            // Save profit of 1 transaction at last v/p pair, if current v is lower than last v.            while (!vp_pairs.empty() && (prices[v] < prices[vp_pairs.top().first]))            {                profits.push_back(prices[vp_pairs.top().second - 1] - prices[vp_pairs.top().first]);                vp_pairs.pop();            }            // Save profit difference between 1 transaction (last v and current p) and 2 transactions             // (last v/p + current v/p), if current v is higher than last v and current p is higher             // than last p.            while (!vp_pairs.empty() && (prices[p - 1] >= prices[vp_pairs.top().second - 1]))             {                profits.push_back(prices[vp_pairs.top().second - 1] - prices[v]);                v = vp_pairs.top().first;                vp_pairs.pop();            }            vp_pairs.push(pair<int, int>(v, p));        }        // Save profits of the remaining v/p pairs.        while (!vp_pairs.empty())         {            profits.push_back(prices[vp_pairs.top().second - 1] - prices[vp_pairs.top().first]);            vp_pairs.pop();        }        if (k >= profits.size())        {            // Since we have no more than k profit pairs, the result is the sum of all pairs.            ret = accumulate(profits.begin(), profits.end(), 0);        }        else        {            // Move the k highest profits to the end and the average time complexity should be O(n).            nth_element(profits.begin(), profits.begin() + profits.size() - k, profits.end());            // Sum up the k highest profits.            ret = accumulate(profits.begin() + profits.size() - k, profits.end(), 0);        }        return ret;    }};

这里还有个值得注意的操作,原po给出的代码,将profit存在一个max heap里,用priority queue实现,最后依次导出前k大的数据。复杂度为O(nlogn + klogn)。但事实上可以改进为上述模式,我们只需知道前k个的和,至于顺序无所谓,所以实际上是个kth partition问题,不需要O(nlogn)来维护一个heap,可以用O(n)找出第k大的数就行,这里有stl的nth_element可以用。十分方便。

//———————————————————————————————–
STL 简记:
accumulate:

int main(){    std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};    int sum = std::accumulate(v.begin(), v.end(), 0);    int product = std::accumulate(v.begin(), v.end(), 1, std::multiplies<int>());}

accumulate可以方便的用来做连续的求和和乘积。如上述代码所示。前两个参数指明起始位置,第三个参数是initial value。第二个参数可以用来指明是加法(默认)还是乘法。

nth_element:

int main(){    std::vector<int> v{5, 6, 4, 3, 2, 6, 7, 9, 3};    std::nth_element(v.begin(), v.begin() + v.size()/2, v.end());    std::cout << "The median is " << v[v.size()/2] << '\n';    std::nth_element(v.begin(), v.begin()+1, v.end(), std::greater<int>());    std::cout << "The second largest element is " << v[1] << '\n';}

也就是之前leetcode里的第k大的数的标准调用函数。基于partition算法,复杂度O(n).
参数需要指明,起始位置,第几个,终点位置。
//——————————————————————————————-

方法二:
这题更通用的解法是dynamic programming。代码如下:

int maxProfit(int k, vector<int>& prices) {    int len = prices.size();    if (len <= 1) return 0;    if (k >= len / 2) return quicksolve(prices);    vector<vector<int>> profit(k + 1, vector<int>(len, 0));    for (int i = 1; i <= k; i++) {        int balance = -prices[0];        for (int j = 1; j < len; j++) {            profit[i][j] = max(profit[i][j - 1], balance + prices[j]);            balance = max(balance, profit[i - 1][j - 1] - prices[j]);        }    }    return profit[k][len - 1];}int quicksolve(vector<int>& prices) {    int len = prices.size();    int maxprofit = 0;    for (int i = 1; i < len; i++) {        if (prices[i] > prices[i - 1]) {            maxprofit += prices[i] - prices[i - 1];        }    }    return maxprofit;}

dp的最基本思想是对新的价格,要么买进,要么卖出。我们来看profit[i][j]的产生。profit[i][j]代表在0到j个prices之内做i次交易能产生的最大利润。如果这一点买入j的话,不可能是一个增加profit的选项,所以profit[i][j]要么等于profit[i][j-1],就是j这一天什么都不做。要么就是在这一天把j卖出。可是卖出我们并不知道对应哪一次买入。如何卖出才能维护一个最大利润了?我们需要记录当前最大的balance。balance的更新取决于每一个的买入。是想,本来有一个balance在那,新的一天出现的时候,balance要么就不变,要么就是在这一天产生新的买入,也就是profit[i-1][j-1] - prices[j]. 所以balance的存在就是为了下一次卖出做准备的,就是记录当前卖出之前的,最划算的买入时机。

阅读全文
1 0