当前位置:首页 > 公众号精选 > 程序员小灰
[导读]前一段时间,小灰发布了上下两篇关于股票买卖的算法题讲解,激发了很多小伙伴的兴趣。 这一次,小灰把这两篇漫画整合在一起,并且修改了其中的一些细节错误。

前一段时间,小灰发布了上下两篇关于股票买卖的算法题讲解,激发了很多小伙伴的兴趣。


这一次,小灰把这两篇漫画整合在一起,并且修改了其中的一些细节错误,感谢小伙伴们的指正。



—————  第二天  —————



什么意思呢?让我们来举个例子,给定如下数组:



该数组对应的股票涨跌曲线如下:


显然,从第2天价格为1的时候买入,从第5天价格为8的时候卖出,可以获得最大收益:



此时的最大收益是 8-1=7。






在上面这个例子中,最大值9在最小值1的前面,我们又该怎么交易?总不能让时间倒流吧?



————————————



以下图为例,假如我们已经确定价格4的时候为卖出时间点,那么此时最佳的买入时间点是哪一个呢?


我们要选择价格4之前的区间,且必须是区间内最小值,显然,这个最佳的选择是价格2的时间点。




第1步,初始化操作,把数组的第1个元素当做临时的最小价格;最大收益的初始值是0:



第2步,遍历到第2个元素,由于2<9,所以当前的最小价格变成了2;此时没有必要计算差值的必要(因为前面的元素比它大),当前的最大收益仍然是0:



第3步,遍历到第3个元素,由于7>2,所以当前的最小价格仍然是2;如我们刚才所讲,假设价格7为卖出点,对应的最佳买入点是价格2,两者差值7-2=5,5>0,所以当前的最大收益变成了5:




第4步,遍历到第4个元素,由于4>2,所以当前的最小价格仍然是2;4-2=2,2<5,所以当前的最大收益仍然是5:



第5步,遍历到第5个元素,由于3>2,所以当前的最小价格仍然是2;3-2=1,1<5,所以当前的最大收益仍然是5:



以此类推,我们一直遍历到数组末尾,此时的最小价格是1;最大收益是8-1=7:



public class StockProfit {

    public static int maxProfitFor1Time(int prices[]) {
        if(prices==null || prices.length==0) {
            return 0;
        }
        int minPrice = prices[0];
        int maxProfit = 0;
        for (int i = 1; i < prices.length; i++) {
            if (prices[i] < minPrice) {
                minPrice = prices[i];
            } else if(prices[i] - minPrice > maxProfit){
                maxProfit = prices[i] - minPrice;
            }
        }
        return maxProfit;
    }

    public static void main(String[] args) {
        int[] prices = {9,2,7,4,3,1,8,4};
        System.out.println(maxProfitFor1Time(prices));
    }

}


我们以下图这个数组为例,直观地看一下买入卖出的时机:



在图中,绿色的线段代表价格上涨的阶段。既然买卖次数不限,那么我们完全可以在每一次低点都进行买入,在每一次高点都进行卖出。


这样一来,所有绿色的部分都是我们的收益,最大总收益就是这些局部收益的加总:


(6-1)+(8-3)+(4-2)+(7-4) = 15


至于如何判断出这些绿色上涨阶段呢?很简单,我们遍历整个数组,但凡后一个元素大于前一个元素,就代表股价处于上涨阶段。


    public int maxProfitForAnyTime(int[] prices) {
        int maxProfit = 0;
        for (int i = 1; i < prices.length; i++) {
            if (prices[i] > prices[i-1])
                maxProfit += prices[i] - prices[i-1];
        }
        return maxProfit;
    }




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



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


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



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


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





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


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




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


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


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


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



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



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


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



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


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


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


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


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


没有买卖

第1次买入

第1次卖出

第2次买入

第2次卖出


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


最大收益 = F(n,m)(n>=1,0<=m<=4


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


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


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



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


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


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


4.如果有2次买入,也就是m=3时,同m=1的情况,F(1,3)= -price[0]


5.如果有2次卖出,也就是m=4时,同m=2的情况,F(1,4)= 0



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


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


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


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


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


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


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


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


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


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


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


最后,我们把情况【2,3】,【4,5】,【6、7】,【8,9】合并,可以总结成下面的5个方程式:


F(n,0) = 0

F(n,1)=  max(-price[n-1],F(n-1,1)

F(n,2)=  max(F(n-1,1)+ price[n-1],F(n-1,2)

F(n,3)=  max(F(n-1,2)- price[n-1],F(n-1,3)

F(n,4)=  max(F(n-1,3)+ price[n-1],F(n-1,4)


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


F(n,m) = max(F(n-1,m-1)+ price[n-1],F(n-1,m))


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






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


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



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



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


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



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



F(2,2)= max(F(1,1)+2,F(1,2))= max(1,0)= 1,所以第2行第3列的结果是1:



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



F(2,4)= max(F(1,3)+2,F(1,4))= max(1,0)= 1,所以第2行第5列的结果是1:



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



接下来填充第4行:



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



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


    //最大买卖次数
    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            for(int j=1;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];
    }




    //最大买卖次数
    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            for(int j=1;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从二维数组简化成了一维数组。由于最大买卖次数是常量,所以算法的空间复杂度也从O(n)降低到了O(1)。




    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;i2) {
            resultTable[i] = -prices[0];
        }
        //自底向上,填充数据
        for(int i=1;i            for(int j=1;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];
    }




—————END—————



喜欢本文的朋友,欢迎关注公众号 程序员小灰,收看更多精彩内容

      
点个[在看],是对小灰最大的支持!


免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

北京2024年5月16日 /美通社/ -- 5月10日晚,2024中国品牌日晚会在新华社全媒体矩阵播出,50个入选品牌在当晚揭晓,极氪名列其中,成为首度上榜的中国造车新势力代表。 图为北京时间5月10日 21:30 ,...

关键字: 吉利 COM HTML 代码

随着科技的飞速进步,人工智能(AI)已经逐渐成为了引领新一轮科技革命和产业变革的核心驱动力。AI不仅在改变着我们的日常生活,还在推动各行各业的创新发展。展望未来,人工智能的发展将呈现出哪些趋势呢?本文将从技术、应用、伦理...

关键字: 人工智能 算法 AI技术

机器学习算法不会要求一个问题被 100%求解,取而代之的是把问题转化为最优化的问题,用不同的算法优化问题,从而比较得到尽量好的结果。

关键字: 机器学习 算法 最优化

据数据类型的不同,对一个问题的建模有不同的方式。在机器学习或者人工智能领域,人们首先会考虑算法的学习方式。在机器学习领域,有几种主要的学习方式。

关键字: 机器学习 人工智能 算法

NVIDIA 量子模拟平台将通过各大云提供商提供,帮助科学家推进量子计算和算法研究

关键字: 量子计算 算法 量子云

随着科技的飞速发展,人工智能(AI)已经成为当今科技研究的热点和前沿。AI的快速发展不仅带来了许多新的应用场景和商业模式,也在推动科技进步的同时,引发了一系列关于其未来发展方向和潜在影响的深入讨论。本文将对人工智能的科技...

关键字: 人工智能 AI技术 算法

机器学习算法:机器学习是一种让计算机通过学习数据和模式来改进自身算法的技术。这些算法包括监督学习、无监督学习和强化学习。

关键字: 人工智能 机器学习 算法

随着信息技术的快速发展,机器学习作为人工智能的核心技术之一,正逐渐渗透到各个领域,引领着一场前所未有的科技变革。在机器学习的实际应用中,有三大重点至关重要,它们分别是数据质量、算法选择与模型评估。本文将深入探讨这三大重点...

关键字: 机器学习 数据质量 算法

在人工智能的浪潮中,机器学习已逐渐成为推动科技进步的核心动力。机器学习技术的广泛应用,从图像识别到自然语言处理,从智能推荐到自动驾驶,都离不开其三个基本要素:数据、算法和模型。本文将深入探讨这三个基本要素在机器学习中的作...

关键字: 机器学习 算法 人工智能

随着信息技术的迅猛发展,机器学习作为人工智能的核心技术之一,已经深入到了各个领域,为我们的生活和工作带来了翻天覆地的变化。无论是智能语音助手、自动驾驶汽车,还是个性化推荐、疾病预测,这些令人惊叹的应用背后,都离不开机器学...

关键字: 机器学习 人工智能 算法
关闭
关闭