漫画:寻找股票买入卖出的最佳时机(整合版)(下)

简介: 让我们来举个例子,给定如下数组:该数组对应的股票涨跌曲线如下:显然,从第2天价格为1的时候买入,从第5天价格为8的时候卖出,可以获得最大收益。


我们仍然以之前的数组为例:

 

640.png

 

首先,寻找到1次买卖限制下的最佳买入卖出点:

 640.png

两次买卖的位置是不可能交叉的,所以我们找到第1次买卖位置后,把这一对买入卖出点以及它们中间的元素全部剔除掉:

 640.png

 

接下来,我们按照同样的思路,再从剩下的元素中寻找第2次买卖的最佳买入卖出点:

 640.png

这样一来,我们就找到了最多2次买卖情况下的最佳选择:

640.png640.png640.png640.png640.png640.png


对于上图的这个数组,如果独立两次求解,得到的最佳买入卖出点分别是【1,9】和【6,7】,最大收益是 (9-1+7-6=9

 

640.png

但实际上,如果选择【1,8】和【3,9】,最大收益是(8-1+9-3=13>9

 640.png

640.png640.png640.png


当第1次最佳买入卖出点确定下来,第2次买入卖出点的位置会有哪几种情况呢?

情况1:第2次最佳买入卖出点,在第1次买入卖出点左侧

 

640.png

情况2:第2次最佳买入卖出点,在第1次买入卖出点右侧

 

640.png

情况3:第1次买入卖出区间从中间截断,重新拆分成两次买卖

 640.png

 

那么,如何判断给定的股价数组属于那种情况呢?很简单,在第1次最大买入卖出点确定后,我们可以比较一下哪种情况带来的收益增量最大:

640.png

 

寻找左侧和右侧的最大涨幅比较好理解,寻找中间的最大跌幅有什么用呢?

细想一下就能知道,当第1次买卖需要被拆分开来的时候,买卖区间内的最大跌幅,就等同于拆分后的收益增量(类似于炒股的抄底操作):

 

640.png

 

这样一来,我们可以总结出下面的公式:

最大总收益 = 1次最大收益 + Max(左侧最大涨幅,中间最大跌幅,右侧最大涨幅)

640.png640.png



所谓动态规划,就是把复杂的问题简化成规模较小的子问题,再从简单的子问题自底向上一步一步递推,最终得到复杂问题的最优解。

首先,让我们分析一下当前这个股票买卖问题,这个问题要求解的是一定天数范围内、一定交易次数限制下的最大收益。

既然限制了股票最多买卖2次,那么股票的交易可以划分为5个阶段:

没有买卖

1次买入

1次卖出

2次买入

2次卖出

我们把股票的交易阶段设为变量m(用从04的数值表示),把天数范围设为变量n。而我们求解的最大收益,受这两个变量影响,用函数表示如下:

最大收益 = Fnm)(n>=10<=m<=4

 

既然函数和变量已经确定,接下来我们就要确定动态规划的两大要素:


 

1.问题的初始状态

2.问题的状态转移方程式

问题的初始状态是什么呢?我们假定交易天数的范围只限于第1天,也就是n=1的情况:

 

 640.png

1.如果没有买卖,也就是m=0时,最大收益显然是0,也就是 F1,0= 0

2.如果有1次买入,也就是m=1时,相当于凭空减去了第1天的股价,最大收益是负的当天股价,也就是 F1,1= -price[0]

3.如果有1次买出,也就是m=2时,买卖抵消(当然,这没有实际意义),最大收益是0,也就是 F1,2= 0

4.如果有2次买入,也就是m=3时,同m=1的情况,F1,3= -price[0] 5.如果有2次卖出,也就是m=4时,同m=2的情况,F1,4= 0

确定了初始状态,我们再来看一看状态转移。假如天数范围限制从n-1天增加到n天,那么最大收益会有怎样的变化呢?

 640.png

这取决于现在处于什么阶段(是第几次买入卖出),以及对第n天股价的操作(买入、卖出或观望)。让我们对各个阶段情况进行分析:

1.假如之前没有任何买卖,而第n天仍然观望,那么最大收益仍然是0,即 Fn0 = 0

2.假如之前没有任何买卖,而第n天进行了买入,那么最大收益是负的当天股价,即 Fn1= -price[n-1]

3.假如之前有1次买入,而第n天选择观望,那么最大收益和之前一样,即 Fn1= Fn-11

4.假如之前有1次买入,而第n天进行了卖出,那么最大收益是第1次买入的负收益加上当天股价,即那么 Fn2= Fn-11+ price[n-1]

5.假如之前有1次卖出,而第n天选择观望,那么最大收益和之前一样,即 Fn2= Fn-12

6.假如之前有1次卖出,而第n天进行2次买入,那么最大收益是第1次卖出收益减去当天股价,即Fn3= Fn-12 - price[n-1]

7.假如之前有2次买入,而第n天选择观望,那么最大收益和之前一样,即 Fn3= Fn-13

8.假如之前有2次买入,而第n天进行了卖出,那么最大收益是第2次买入收益减去当天股价,即Fn4= Fn-13 + price[n-1]

9.假如之前有2次卖出,而第n天选择观望(也只能观望了),那么最大收益和之前一样,即 Fn4= Fn-14

最后,我们把情况【23】,【45】,【67】,【89】合并,可以总结成下面的5个方程式:

Fn0 = 0

Fn1=  max-price[n-1]Fn-11))

Fn2=  maxFn-11+ price[n-1]Fn-12))

Fn3=  maxFn-12- price[n-1]Fn-13))

F(n4=  maxFn-13+ price[n-1]Fn-14))

从后面4个方程式中,可以总结出每一个阶段最大收益和上一个阶段的关系:

F(nm = maxFn-1m-1+ price[n-1]Fn-1m))

由此我们可以得出,完整的状态转移方程式如下:

640.png

640.png640.png640.png640.png


在表格中,不同的行代表不同天数限制下的最大收益,不同的列代表不同买卖阶段的最大收益。

我们仍然利用之前例子当中的数组,以此为基础来填充表格:

 640.png

首先,我们为表格填充初始状态:

 640.png

接下来,我们开始填充第2行数据。

没有买卖时,最大收益一定为0,因此F2,0)的结果是0

640.png

 

根据之前的状态转移方程式,F2,1= maxF1,0-2F1,1))= max-2-1= -1,所以第2行第2列的结果是-1

 

640.png

 

F2,2= maxF1,1+2F1,2))= max10= 1,所以第2行第3列的结果是1

 640.png 

F(2,3= maxF1,2-2F1,3))= max-2-1= -1,所以第2行第4列的结果是-1

 640.png

F(2,4= maxF1,3+2F1,4))= max10= 1,所以第2行第5列的结果是1

 640.png

接下来我们继续根据状态转移方程式,填充第3行的数据

 640.png

接下来填充第4行:

640.png

 

以此类推,我们一直填充完整个表格:

 640.png

如图所示,表格中最后一个数据13,就是全局的最大收益。

640.png

 

//最大买卖次数
    private static int MAX_DEAL_TIMES = 2;
    public static int maxProfitFor2Time(int[] prices) {
        if(prices==null || prices.length==0) {
            return 0;
        }
        //表格的最大行数
        int n = prices.length;
        //表格的最大列数
        int m = MAX_DEAL_TIMES*2+1;
        //使用二维数组记录数据
        int[][] resultTable = new int[n][m];
        //填充初始状态
        resultTable[0][1] = -prices[0];
        resultTable[0][3] = -prices[0];
        //自底向上,填充数据
        for(int i=1;i<n;++i) {
            for(int j=1;j<m;j++){
                if((j&1) == 1){
                    resultTable[i][j] = Math.max(resultTable[i-1][j], resultTable[i-1][j-1]-prices[i]);
                }else {
                    resultTable[i][j] = Math.max(resultTable[i-1][j], resultTable[i-1][j-1]+prices[i]);
                }
            }
        }
        //返回最终结果
        return resultTable[n-1][m-1];
    }

640.png640.png640.png640.png640.png640.png640.png640.png640.png

 

//最大买卖次数
    private static int MAX_DEAL_TIMES = 2;
    public static int maxProfitFor2TimeV2(int[] prices) {
        if(prices==null || prices.length==0) {
            return 0;
        }
        //表格的最大行数
        int n = prices.length;
        //表格的最大列数
        int m = MAX_DEAL_TIMES*2+1;
        //使用一维数组记录数据
        int[] resultTable = new int[m];
        //填充初始状态
        resultTable[1] = -prices[0];
        resultTable[3] = -prices[0];
        //自底向上,填充数据
        for(int i=1;i<n;++i) {
            for(int j=1;j<m;j++){
                if((j&1) == 1){
                    resultTable[j] = Math.max(resultTable[j], resultTable[j-1]-prices[i]);
                }else {
                    resultTable[j] = Math.max(resultTable[j], resultTable[j-1]+prices[i]);
                }
            }
        }
        //返回最终结果
        return resultTable[m-1];
    }

 

 

       

在这段代码中,resultTable从二维数组简化成了一维数组。由于最大买卖次数是常量,所以算法的空间复杂度也从On)降低到了O1)。

 640.png640.png640.png



public static int maxProfitForKTime(int[] prices, int k) {
        if(prices==null || prices.length==0 || k<=0) {
            return 0;
        }
        //表格的最大行数
        int n = prices.length;
        //表格的最大列数
        int m = k*2+1;
        //使用一维数组记录数据
        int[] resultTable = new int[m];
        //填充初始状态
        for(int i=1;i<m;i+=2) {
            resultTable[i] = -prices[0];
        }
        //自底向上,填充数据
        for(int i=1;i<n;i++) {
            for(int j=1;j<m;j++){
                if((j&1) == 1){
                    resultTable[j] = Math.max(resultTable[j], resultTable[j-1]-prices[i]);
                }else {
                    resultTable[j] = Math.max(resultTable[j], resultTable[j-1]+prices[i]);
                }
            }
        }
        //返回最终结果
        return resultTable[m-1];
    }


相关文章
|
算法
【动态规划刷题 7】 买卖股票的最佳时机含冷冻期&& 买卖股票的最佳时机含手续费
【动态规划刷题 7】 买卖股票的最佳时机含冷冻期&& 买卖股票的最佳时机含手续费
|
6月前
|
算法
算法编程(五):买卖股票的最佳时机
算法编程(五):买卖股票的最佳时机
47 0
|
6月前
|
算法 索引
leetcode代码记录(买卖股票的最佳时机
leetcode代码记录(买卖股票的最佳时机
38 1
|
5月前
|
算法
leetcode题解:121.买卖股票的最佳时机
leetcode题解:121.买卖股票的最佳时机
40 0
|
6月前
|
算法
leetcode代码记录(买卖股票的最佳时机 III
leetcode代码记录(买卖股票的最佳时机 III
35 5
|
6月前
leetcode代码记录(买卖股票的最佳时机 II
leetcode代码记录(买卖股票的最佳时机 II
29 1
|
6月前
|
算法
【力扣】121. 买卖股票的最佳时机、122.买卖股票的最佳时机Ⅱ
【力扣】121. 买卖股票的最佳时机、122.买卖股票的最佳时机Ⅱ
|
6月前
代码随想录 Day43 动态规划11 LeetCode T309 买卖股票的最佳时期含冷冻期 T714买卖股票的最佳时机含手续费
代码随想录 Day43 动态规划11 LeetCode T309 买卖股票的最佳时期含冷冻期 T714买卖股票的最佳时机含手续费
51 0
|
算法
代码随想录算法训练营第五十天 | LeetCode 309. 买卖股票的最佳时机含冷冻期、714. 买卖股票的最佳时机含手续费、股票系列总结
代码随想录算法训练营第五十天 | LeetCode 309. 买卖股票的最佳时机含冷冻期、714. 买卖股票的最佳时机含手续费、股票系列总结
76 1
|
算法
代码随想录算法训练营第四十八天 | LeetCode 121. 买卖股票的最佳时机、122. 买卖股票的最佳时机 II
代码随想录算法训练营第四十八天 | LeetCode 121. 买卖股票的最佳时机、122. 买卖股票的最佳时机 II
74 1