[Leetcode188] 买卖股票的最佳时机IV 动态规划 解题报告

Posted null-0

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Leetcode188] 买卖股票的最佳时机IV 动态规划 解题报告相关的知识,希望对你有一定的参考价值。

题源:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv/

 

本题代码:

 1 /**
 2  * @author yuan
 3  * @version 0.1
 4  * @date 2019/4/5
 5  */
 6 public class Leetcode188 {
 7 
 8     private int getBetterBuy(int[] buy, int[] sell, int price, int i) {
 9         if (i == 0) {
10             return Math.max(buy[i],-price);
11         } else {
12             return Math.max(buy[i], sell[i - 1] - price);
13         }
14     }
15 
16     private int getBetterSell(int[] buy, int[] sell, int price, int i) {
17         return Math.max(sell[i], buy[i] + price);
18     }
19 
20     /** @see Leetcode122 */
21     public int greedy(int[] prices) {
22         int result = 0;
23         // 当前是否持有股票
24         boolean has = false;
25         // 当前所持有股票的购入价
26         int buyInPrice = 0;
27         for (int i = 0; i < prices.length; i++) {
28             if (!has) {
29                 if (i + 1 < prices.length && prices[i + 1] > prices[i]) {
30                     has = true;
31                     buyInPrice = prices[i];
32                 }
33             } else if (i + 1 == prices.length || prices[i + 1] < prices[i]) {
34                 has = false;
35                 result += prices[i] - buyInPrice;
36             }
37         }
38         return result;
39     }
40 
41     /** @see Leetcode123 */
42     public int maxProfit(int k, int[] prices) {
43         if (k <= 0 || prices == null || prices.length == 0) {
44             return 0;
45         }
46         // k足够大的时候退化为贪心
47         if (k >= prices.length / 2) {
48             return greedy(prices);
49         }
50         // 第i次买的收益
51         int[] buy = new int[k];
52         for (int i = 0; i < k; i++) {
53             buy[i] = Integer.MIN_VALUE;
54         }
55         // 第i次卖的收益
56         int[] sell = new int[k];
57         for (int price : prices) {
58             for (int j = 0; j < k; j++) {
59                 buy[j] = getBetterBuy(buy, sell, price, j);
60                 sell[j] = getBetterSell(buy, sell, price, j);
61             }
62         }
63         return sell[k - 1];
64     }
65 }

 

整体思路:

  要进行n次交易,至少需要2n天,如果k大于或等于数组长度的一半,那么这题就退化为买卖股票的最佳时机 II,采用贪心法来做(见Leetcode上的题解)。

  如果k小于数组长度的一半,那么就变成了买卖股票的最佳时机 III的扩展,买卖股票的最佳时机 III动态规划解法如下:

  技术图片

  原出处:https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/discuss/39611/Is-it-Best-Solution-with-O(n)-O(1).

  将这4个变量扩展为2k个变量,用数组保存,以同样方式处理,之后返回最后一次出售的收益即可。

  本题动态规划的具体细节与买卖股票的最佳时机 III相同。

 

本题状态转移方程:

  but[t][i] = max{ buy[t-1][i], sell[t-1][i-1] - prices[t] }

  sell[t][i] = max{ sell[t-1][i], buy[t-1][i] + prices[t] }

  其中t表示天数,i表示交易次数。具体说明见下文。

  t这个维度可以用滚动数组省去。

 

算法细节:

  将买卖股票的最佳时机 III的动态规划结合买卖股票的最佳时机 II的贪心写出这题的代码不难,如果你已经明白买卖股票的最佳时机 III的动态规划算法是怎么回事,那么下面的这一堆字就没必要看了??。下面是对上文本题代码的解释。

  首先要明确buy[i]和sell[i]含义。用t表示考虑前t天的情况(从0开始),并将在最晚第t天进行至多第i次购入的收益表示为buy[t][i],在最晚第t天进行至多第i次出售的收益表示为sell[t][i]。显然购入操作的将带来负收益。题目给的k是指最多进行k次交易,而最优解的交易次数可能是小于k的,因此buy[t][i]和sell[t][i]的含义并不是第i次购入或出售后的收益,而是指在该天进行第[小于或等于i]次购入或出售后的收益。比如k=2,prices={2,4,1,0,0,0,0,0}的情况,显然第1天购入第2天出售可以获得最大收益,最终结果sell[t=7][i=1]应与sell[t=1][i=0]的值相同。也就是说,如果最优解一共进行了x次交易,且这次交易的出售在第y天进行,那么对所有i>=x,t>=y,buy[t][i]与sell[t][i]必须分别等于buy[y][x]和sell[y][x],最终结果才能用sell[prices.length-1][k-1]表示。采用滚动数组可以省去天数这个维度,但下文部分地方为方便说明,仍会把天数标出。

  明确buy[]和sell[]的含义后,状态转移方程本身是否正确比较好理解,这里不进行解释。

  第i次出售必须在第i次购入之后进行,并且每天只能进行一次操作,那么每天都计算整个buy[]和sell[]数组不会出错吗?并不会。

  假设一天可以进行多次操作,如果同一天各一次买入和卖出,这两次操作是不会有收益的,所以在第一天计算第一次出售的收益会是0。既然在第一天进行第一次出售的收益是0,那么第一天进行第二次购入的收益就将和第一次购入的收益相同,如果闲得蛋疼,买一次卖一次再买一次再卖一次,最后再买第三次,产生的收益也和第一次购入相同。如果第一天进行了第一次购入,第二天进行第一次售出后再进行第二次购入,那么就相当于这一天什么也没做,总收益仍然等于第一天的第一次购入产生的收益。依此类推不难发现,如果在每天只能操作依次时当前这一天至多只能进行第x次购入,那么取消限制后,对于所有i>x,buy[i]是等于buy[x]的,并且sell[x]等于sell[x-1],接着可以推出所有这些sell[i]都等于sell[x-1]。因此,对于每个prices[i],即每一天,都对整个buy[]和sell[]数组进行计算,可以使得sell[k-1]等于最优解的收益。于是sell[k-1]就是我们要的结果。

  以k=2,prices={2,4,1,1,0}为例说明:

  首先初始化buy[]为全-∞(Java中用Integer.MIN_VALUE模拟),sell[]为全0。

  在第1天(t=0),buy[0]显然会等于-price[0],即-2。

  此时虽然还不能产生第1次出售,但sell[0]=max{sell[0],buy[0]+prices[0]},结果将会是0。

  此时虽然还不能产生第2次购入,但buy[1]=max{buy[1],sell[0]-prices[0]},将和buy[0]相等。

  此时虽然还不能产生第2次出售,但sell[1]=max{sell[1],buy[1]+prices[0]},结果也会是0。实际上既然buy[1]和buy[0]相等,sell[1]也就等于sell[0]。

  于是,如果只考虑第1天,最大收益sell[1]是0,结果正确。

  再考虑第2天(t=1)。

  buy[0]=max{buy[0],sell[0]-prices[1]},结果仍然是-2。

  sell[0]=max{sell[1],buy[0]+prices[1]},结果为2。

  此时虽然还不能产生第2次购入,但由于buy[0][1]与buy[0][0]相等,且buy[1][1]=max{buy[0][1],sell[1][0]-prices[1]},相当于buy[1][1]=max{buy[0][0],sell[0][0]-prices[1]}。最终buy[1][1]和buy[1][0]相等,即第二天的buy[1]仍然和buy[0]相等,值为-2。

  此时虽然还不能产生第2次出售,但sell[1]=max{sell[1],buy[1]+prices[1]},结果也是2。

  在只考虑前2天的情况下,最大收益sell[1]是2,结果正确。

  再考虑第3天。

  buy[0]=max{buy[0],-prices[2]},变为-1。

  sell[0]=max{sell[0],buy[0]+prices[2]},仍为2。

  buy[1=max{buy[1],sell[0]-prices[2]},变为1。

  此时虽然还不能产生第2次出售,sell[1]=max{sell[1],buy[1]+prices[2]},结果仍为2。

  于是只考虑前3天的情况下,最大收益sell[1]为2,结果正确。

  再考虑第4天。

  buy[0]=max{buy[0],-prices[3]},仍为-1。

  sell[0]=max{sell[0],buy[0]+prices[3]},仍为2。

  buy[1]=max{buy[1],sell[0]+prices[3]},仍为1。

  sell[1]=max{sell[1],buy[1]+prices[3]},仍为2。

  于是只考虑前4天的情况下,最大收益sell[1]为2,结果正确。

  最后考虑第5天

  buy[0]=max{buy[0],-prices[4]},变为0。

  sell[0]=max{sell[0],buy[0]+prices[4]},仍为2。

  buy[1]=max{buy[1],sell[0]+prices[4]},变为2。

  sell[1]=max{sell[1],buy[1]+prices[4]},仍为2。

  最终最大收益sell[1]为2,结果正确。

 

附另外两题的代码

 1 /**
 2  * @author yuan
 3  * @version 0.1
 4  * @date 2019/4/5
 5  */
 6 public class Leetcode122 {
 7     public int maxProfit(int[] prices) {
 8         int result = 0;
 9         int buyInPrice = 0;
10         boolean has = false;
11         for (int i = 0; i < prices.length; i++) {
12             if (!has) {
13                 if (i + 1 < prices.length && prices[i + 1] > prices[i]) {
14                     has = true;
15                     buyInPrice = prices[i];
16                 }
17             } else if (i + 1 == prices.length || prices[i + 1] < prices[i]) {
18                 has = false;
19                 result += prices[i] - buyInPrice;
20             }
21         }
22         return result;
23     }
24 }
 1 /**
 2  * @author yuan
 3  * @version 0.1
 4  * @date 2019/4/5
 5  */
 6 public class Leetcode123 {
 7     public int maxProfit(int[] prices) {
 8         /*
 9          对于任意一天考虑四个变量:
10          firstBuy: 在该天第一次买入股票可获得的最大收益
11          firstSell: 在该天第一次卖出股票可获得的最大收益
12          secondBuy: 在该天第二次买入股票可获得的最大收益
13          secondSell: 在该天第二次卖出股票可获得的最大收益
14          分别对四个变量进行相应的更新, 最后secSell就是最大
15          收益值(secondSell >= firstBuy)
16          */
17         int firstBuy = Integer.MIN_VALUE;
18         int firstSell = 0;
19         int secondBuy = Integer.MIN_VALUE;
20         int secondSell = 0;
21         for (int price : prices) {
22             firstBuy = Math.max(firstBuy, -price);
23             // 如果在第一天,这个结果是0
24             firstSell = Math.max(firstSell, firstBuy + price);
25             // 如果在第一天,将等于firstBuy;如果在第二天,由于第一天等于firstBuy,仍将等于firstBuy
26             secondBuy = Math.max(secondBuy, firstSell - price);
27             // 如果在前两天,由于secondBuy等于firstBuy,结果等于firstSell;如果在第三天,secondBuy + price等于firstSell
28             secondSell = Math.max(secondSell, secondBuy + price);
29         }
30         return secondSell;
31     }
32 }

 

以上是关于[Leetcode188] 买卖股票的最佳时机IV 动态规划 解题报告的主要内容,如果未能解决你的问题,请参考以下文章

Leetcode No.188 买卖股票的最佳时机 IV(动态规划)

LeetCode 0188. 买卖股票的最佳时机 IV

188. 买卖股票的最佳时机 IV

188.买卖股票的最佳时机IV

188.买卖股票的最佳时机IV

LeetCode ---- 买卖股票系列问题思路与题解