Best Time to Buy and Sell Stock IV

来源:互联网 发布:淘宝客服售前售后技巧 编辑:程序博客网 时间:2024/06/13 00:55

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

分析:

这个题把一次买卖看做交易一次,实际上是求k个不相交的连续子序列的最大和。

动态规划解法

当k>=N/2,实质上可以买卖无限多次,同第二题。
当k<N/2时,需要DP
设原始数据存于prices[1…n]数组中
f[i][k]表示用prices中的[1..i]不超过i天的数据,最多交易k次的最大收益。
转移方程为:
f[i][k] = max(f[i][k-1],max(f[i-1][k],f[j-1][k-1] + prices[i] - prices[j]))
移项后得到:
f[i][k] = max(f[i][k-1],max(f[i-1][k],prices[i] + (f[j-1][k-1] - prices[j]) ))  (1<=j<=i)

当上一次遍历i的时候实际上f[j-1][k-1]就已经算出来了。
(f[j-1][k-1]-prices[j])用变量max代替,max可以边计算边维护,所有时间算法复杂度为O(kN).

    public int maxProfit(int k, int[] prices) {        int n = prices.length;        if (k >= n / 2) {            return allSell(prices);        }        int dp[][] = new int[n + 1][k + 1];        int p[] = new int[n + 1];        for (int i = n; i >= 1; i--) p[i] = prices[i - 1];        // f[i][k] = max(f[i][k-1],max(f[i-1][k],prices[i] + (f[j-1][k-1] - prices[j]) ))        for (int t = 1; t <= k; t++) {            int max = Integer.MIN_VALUE;            for (int i = 1; i <= n; i++) {                dp[i][t] = Math.max(dp[i][t - 1], Math.max(max + p[i], dp[i - 1][t]));                max = Math.max(max, dp[i - 1][t - 1] - p[i]);            }        }        return dp[n][k];    }



线性复杂度方法:

价格向量为a天数为n。问题要求给出k′ ≤ k买入时刻vk卖出时刻p,满足v0 < p0 < v1 < p1 < … < vk′ − 1 < pk′ − 1,使i(api − avi)最大。

另外,这个问题等价于求出a的差分向量bi = ai + 1 − aik不重叠最大子段和。下面给出一个线性算法,但没有用到b

不失一般性,可以要求每个vi局部极小值,满足vi = 0avi − 1 ≥ avi,且avi < avi + 1;每个pi局部极大值,满足api − 1 ≤ api,且pi = n − 1api > api + 1。若最优方案不满足这个条件,可以调整vp以满足这个条件,仍保持最优性。

先线性扫描a,得到所有(上文定义的)局部极小和极大值。把它们按指标(a向量中位置)顺序排序,指标最小的若是局部极大值则舍弃,指标最大的若是局部极小值也舍弃。余下的序列满足局部极小和局部极大交替出现,且局部极小值先出现。设该序列为v0 < p0 < v1 < p1 < … < vg − 1 < pg − 1,长度为2g,把vipi配对,称为一个pv对(valley和peak),性质是avi < api

下面研究一下最佳方案的性质。

对于一个pv对(vi, pi),第vi天会买入或不作操作,不会卖出;第pi天会卖出或不作操作,不会买入。一个pv对并非捆绑的,即第vi天买入不意味着在第pi天卖出,可以在之后的某个pv对卖出。最佳买入时刻和卖出时刻(不超过k对)一定在这2g个指标里。

(vi, pi)的利益为api − avi。我们设法变换这g个pv对,得到若干不超过g个新的pv对(可能会有vi > pi,也不再保证avi < api),使得按利益从大到小选取不超过k个(因为可能有负收益),其利益和等于题目所求的最大收益。变换过程中的所有pv对称为候选pv对

考虑相邻两个候选pv对:(vi, pi)(vi + 1, pi + 1),四个指标对应四个元素:avi, api, avi + 1, api + 1。若4个数互不相等,则用0~3表示相对大小,有6种情形:

  • 0123。由于api < avi + 1,不应在第pi天卖出后在第vi + 1天买入。若两相邻候选pv对满足此情形,可保证1处不会卖出,2处不会买入,因此可以把这两个pv对替换成一个pv对:(vi, pi + 1),称该操作为合并。合并操作使pv对数目减少一,可以看作01的peak改变了,但其valley不变。
  • 0213。可以把这两个pv对变换成:(vi, pi + 1), (pi + 1, vi),注意第二个pv对的pv颠倒了,称这个操作为重组。重组后pv对数目不变,收益和守恒,但重组后两pv对的收益较大值增大了。若两个pv对只能取一个,则应取收益较大的那个,因此重组是有利的。重组操作的valley也不变。枚举四个点可能取的操作,可以发现重组不会使最佳方案变差。
  • 2301。特征是前一个pv对的valley小于后一个pv对的valley。考虑最佳方案可能在四个点取的操作:

    • 只有一个买入点。应放在0处。
    • 只有一个卖出点。应放在3处。
    • 一个卖出点后跟一个买入点。应放在3处和0处。
    • 一个买入点后跟一个卖出点。两种可能:2处和3处,或0处和1处。
    • 其他。分析方法和上面类似。
    另外在这种情形下,23和之后的某个pv对合并或重组并非最优。合并或重组满足valley不变,即使01与之后的pv对合并或重组了,这两个pv对也不会再满足合并或重组条件。
  • 1203。和上一种情形特征相同,结论相同。
  • 1302。和上一种情形特征相同,结论相同。
  • 0312。合并或重组并非最优。但12可能会与之后的pv对合并或重组,回到0123或0213两种情形,从而满足合并或重组条件。

4个数若有相等,亦可归入上述6种情形。

注意合并和重组都满足valley不变,且有益的合并或重组会使peak增大,使得更容易满足完成未来的合并或重组条件。这说明只要合并或重组有益,就应贪心进行。

另外“一旦不满足合并或重组条件,则之后也不再满足”。我们可以用栈维护候选pv对,表示可能会和未来的某个pv对重组或合并。从栈底到栈顶的各pv对满足三个单调性:一是位置单调递增,二是相邻两pv对(vi, pi), (vi + 1, pi + 1)满足avi < avi + 1,三是api > api + 1

栈初始为空,另外维护一个队列。按指标的递增顺序扫描各候选pv对,若栈顶与扫描到的pv对满足2301、1203或1302(特征是前一个pv对的valley小于后一个pv对的valley),则应弹出栈顶,插入到队列中。弹出若干pv对直到满足不再出现这三种情形。

假如栈非空且栈顶与扫描到的pv对满足0123或0213,则应根据之前的说明进行合并或重组,弹出的pv对都应插入到队列里。弹出若干pv对直到满足不再出现这两种情形。然后把扫描到的pv对压栈。

所有pv对扫描完后,栈中相邻元素满足0312,全部弹出并插入到队列里。

队列中所有元素即为变换后的候选pv对。按利益从大到小排序,选取不超过k个利益非负的,其利益和等于题目所求的最大收益。这一步可以用quick select(C++的nth_element)在线性时间内计算:选取利益第k大的,则排在它前面的元素收益都不小于它。


// Best Time to Buy and Sell Stock IVclass Solution {public:  int maxProfit(int k, vector<int> &a) {    int n = (int)a.size();    stack<pair<int,int>> vp;    vector<int> g;    for (int v = 0, p; v < n; v = p+1) {      for (; v+1 < n && a[v] >= a[v+1]; v++); // local minimum (valley)      for (p = v+1; p+1 < n && a[p] <= a[p+1]; p++); // local maximum (peak)      if (p >= n) break;      // exclude 2-3...1-4 and 2-4...1-3      for (; ! vp.empty() && a[vp.top().first] >= a[v]; vp.pop())        g.push_back(a[vp.top().second]-a[vp.top().first]);      // 1-2...3-4 => 1-4 + 2-3 | 1-3...2-4 => 1-4 + 3-2      for (; ! vp.empty() && a[vp.top().second] <= a[p]; vp.pop()) {        int x = a[vp.top().second]-a[v];        if (x > 0)          g.push_back(x);        v = vp.top().first;      }      vp.push(make_pair(v, p));    }    for (; ! vp.empty(); vp.pop())      g.push_back(a[vp.top().second]-a[vp.top().first]);    k = min(k, (int)g.size());    if (k > 0)      nth_element(g.begin(), g.begin()+k-1, g.end(), greater<int>()); // nth \in [first,last] in C++14, \in [first,last) in C++11    return accumulate(g.begin(), g.begin()+k, 0);  }};/// http://codeforces.com/problemset/problem/391/F3#define FOR(i, a, b) for (int i = (a); i < (b); i++)#define REP(i, n) FOR(i, 0, n)class Solution {public:  int maxProfit(int k, vector<int> &a) {    int n = (int)a.size(), nn = 0;    REP(i, n-1) {      a[nn] = a[i+1]-a[i];      if (! nn && a[0] <= 0)        ;      else if (nn && (a[nn-1] > 0) == (a[nn] > 0))        a[nn-1] += a[nn];      else        nn++;    }    while (nn && a[nn-1] <= 0) nn--;    if (! nn) return 0;    vector<int> L(nn+1), R(nn+1);    vector<pair<int, int>> vp(nn);    vector<bool> flag(nn+1, false), in(nn+1, false);    REP(i, nn)      L[i] = i-1, R[i] = i+1;    L[0] = nn, R[nn] = 0;    L[nn] = nn-1;    stack<int> can;    for(;;) {      int nvp = 0;      for (int i = R[nn]; i != nn; i = R[i])        if (! flag[i])          vp[nvp++] = make_pair(abs(a[i]), i);      if (nvp <= 2*k) break;      int rm = (nvp-2*k+1)/2;      nth_element(vp.begin(), vp.begin()+rm-1, vp.begin()+nvp);      auto pivot = vp[rm-1];      auto add = [&](int x) {        if (! in[x] && make_pair(abs(a[x]), x) <= pivot) {          in[x] = true;          can.push(x);        }      };      for (int i = R[nn]; i != nn; i = R[i])        add(i);      while (! can.empty()) {        int x = can.top();        can.pop();        in[x] = false;        int l = L[x], r = R[x];        if (flag[x] ||            l < nn && abs(a[x]) > abs(a[l]) ||            r < nn && abs(a[x]) > abs(a[r]))          continue;        flag[l] = flag[r] = true;        L[x] = l < nn ? L[l] : nn;        R[x] = r < nn ? R[r] : nn;        R[L[x]] = L[R[x]] = x;        a[x] += a[l] + (r < nn ? a[r] : 0);        if (l == nn) {          flag[x] = true;          L[R[nn] = R[x]] = nn;        }        if (r == nn) {          flag[x] = true;          R[L[nn] = L[x]] = nn;        }        if (L[x] < nn) add(L[x]);        if (R[x] < nn) add(R[x]);        add(x);      }    }    int sum = 0;    for (int i = R[nn]; i != nn; i = R[i])      if (a[i] > 0)        sum += a[i];    return sum;  }};



0 0
原创粉丝点击