【不习算法】从股票买卖看动态规划:Leetcode122 买卖股票的最佳时机 II

【不习算法】从股票买卖看动态规划

引言

 道生一,一生二,二生三,三生万物。 ——《道德经》
 用最简单的语言,由过去推知未来,这便是动态规划的全部。

问题描述

题目

Leetcode122 买卖股票的最佳时机 II
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
示例:
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
     随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。

强调一个关键点

怎么理解可以多次买卖股票?

比如第一天买入、第二天卖出,然后第三天买入,第四天卖出。也可以第一天买入、第四天卖出。也可以第一天买入,第三天卖出,然后不再持有。

这几种买卖策略都是可行的,可以看出合理的买卖策略只需要满足以下两个条件:

A.任何一个买入行为,必定伴随一个卖出行为,且卖出行为发生的日期必须在买入行为发生日期之后。
B.不能连续发生两次及以上的买入行为或卖出行为,必须是一个买入行为紧随一个卖出行为。

这样分析问题,依然感觉有些混乱,因为考虑的情况依然非常的多,比如买入日期和卖出日期的间隔可能是一天也可能是多天,再比如在一个交易周期种可能发生一次买卖,也可能发生多次买卖…如果不能用一个统一的模型,把这些情况都包含进来,最终得到的模型要么非常复杂,要么边界条件考虑不周全。

如何将这样的问题进行化简用统一的模型进行概括呢?且看接下来的分析——

问题分析与突破

如何将第一天买入、第四天卖出和第一天买入、第二天卖出,这两种不同的交易策略,化为一种统一的模型呢?我们其实可以采用以下的分解操作:

第一天买入、第四天卖出其实可以看成这样的过程:第一天买入、第二天卖出;第二天卖出后马上再买入、第三天再卖出;第三天卖出后马上再买入,第四天再卖出。由于买卖的过程是不产生任何手续费的,所以这样的分解操作是可行的,而且这个复杂的问题就可以用一个统一的行为进行概括:

A.未持有股票时:考虑买入或者不买入
B.持有股票时:持有的股票一定卖出,然后考虑当天买入或者不买入
所以无论持有股票,我们只需要考虑接下来时买还是不买这一个行为了,之前复杂的模型便得到了简化。

特别注意:**只有当一个买入行为和卖出行为同时出现时,真正的交易才实现,收益才产生。**也就是说,只有买入行为和卖出行为同时出现时,才算真正的有过“持有股票”的状态。这样的考虑可以应对一种极端情况,比如买入某个股票后,股票持续下跌,没有卖出机会产生,有于卖出行为未出现,所以真正的交易未实现,不会产生任何的收益。

我们以几个实际的股票案例进行理解:

​ 先考虑两种极端情况,即持续上涨的情况和持续下跌的情况。

案例1: 股价持续上涨

案例1:股价持续上涨

该情况下在第一天买入、第五天卖出,最大收益为7。

按照之前的分析策略,我们可以把这个过程分解成以下的子过程:

①第一天股价为2,买入。profit不发生变化为0;
②股票卖出。第二天股价为5,高于之前的股价,可以考虑再次买入。profit增加5-2=3。
③股票卖出。第三天股价为6,高于之前的股价,可以考虑再次买入。profit增加6-5=1。
④股票卖出。第四天股价为7,高于之前的股价,可以考虑再次买入。profit增加7-6=1。
⑤股票卖出。第四天股价为7,高于之前的股价,可以考虑再次买入。profit增加9-7=2。

收益一共为3+1+1+2=7

案例2: 股价持续下跌

案例2:股价持续下跌

该情况下在不进行任何买卖操作,最大收益为0。

按照之前的分析策略,我们可以把这个过程分解成以下的子过程:

①第一天股价为12,买入起点产生(但这并不代表已经持有股票了,需要后面有一个合适的卖出机会时,这个买入起点才变为持有股票的开始点)。
②第二天股价为8,低于之前的股价12。买入起点失效,而此时的股价作为新的买入起点。profit不发生变化为0。
③第三天股价为7,低于之前的股价8,买入起点失效,而此时的股价作为新的买入起点。profit不发生变化为0。
④第四天股价为5,低于之前的股价7,买入起点失效,而此时的股价作为新的买入起点。profit不发生变化为0。
⑤第四天股价为4,低于之前的股价5,买入起点失效,而此时的股价作为新的买入起点。profit不发生变化为0。

综上,收益始终为0。

​ 在对两种极端情况进行分析之后,下面我们对一般情况进行分析。

案例3: 股价波动变化的一般情况

一般情况可以这么考虑:出现每个局部最低点时买入时,产生可能的买入起点。下一个状态的股价高于局部最低点时卖出股票,并将该股价作为新的局部最低点;下一个状态的股价低于局部最低点时,之前的局部最低买入点失效,而下一个状态的股价作为新的局部最低点。

案例3:股价波动一般情况

整体策略:

设局部最低点为pmin,累计收益为profit。

买卖策略制定:在初始价格作为局部最低点pmin,如果之后的股价pn低于局部最低点pmin,那么局部最低点pmin变为这个新的股价pn,累计收益不发生变化;如果之后的股价pn高于局部最低点pmin,那么累计收益profit增加pn-pmin,并将新的股价作为局部最低点。

具体的操作步骤如下:

①第一天股价为2,pmin为2,profit为0;
②第二天股价为5,高于pmin,profit增加5-2=3,并且pmin变为为5;
③第三天股价为6,高于pmin,profit增加6-5=1,并且pmin变为为6;
④第四天股价为4,低于pmin,并且pmin变为为4;
⑤第五天股价为6,高于pmin,profit增加7-3=3,并且pmin变为为7。

综上:第一天买入第三天卖出,第四天买入第五天卖出,总收益profit为3+1+3=7。

不难看出我们的分析方法其实和实际的股票操作有一定的差异。实际过程中,我们只会找到局部最低点”第一天“,和局部最高点“第三天”,然后算第三天和第一天的差价作为收益。但是在分析过程中,我们则考虑了“第一天买入,第二天卖出;然后马上第二天继续买下,第三天卖出。”在不考虑手续费的情况下,两种方法其实是殊途同规,只是后者将具体的过程描述出来,而前者则注重结果。而这种将具体过程表现出来的分析方法,正与动态规划的思想不谋而合,动态规划所强调的正是以用“特定的状态方程”,由过去的状态推导”未来的状态

那在我们的分析方法中,何谓“过去的状态”?何谓未来的状态?何谓“特定的状态方程”?

首先要明白在这个问题中,需要关注的“状态”是什么?有两个状态是值得我们关注的,一个是我们最关心的累计收入O,另一个是为了得到累计收入而需要知道的局部最低点pm。随着时间的流逝,我们希望通过过去的累计收入O和局部最低点pm,推导出未来的累计收入O和局部最低点pm,而这种推导过程,正是用状态转移方程实现的。

那么这种“以过去知未来”的状态转移方程是什么呢?

未来的局部最低点相较于过去局部最低点的变化,只有两种:一种是未来(下一个状态)的股价pn小于等于当前状态的局部最低点pm,另一种是未来的股价pn大于当前状态的局部最低点pm

当未来的股价pn低于当前状态的局部最低点pm时,处于股价下跌状态,作为一个已经预知股市行情的“先知”,这种情况下时绝对不可以进行股票买卖操作的(需要等待而不进行任何的买卖),所以下一个状态的股价变为新的局部最低点,而累计收入O不发生变化。
p n [ i ] = p m [ i ] p_n[i] = p_m[i] pn[i]=pm[i]

O [ i ] = O [ i − 1 ] O[i] = O[i-1] O[i]=O[i1]

当未来的股价pn高于当前状态的局部最低点pm时,处于股价上涨状态,作为一个已经预知股市行情的“先知”,这种情况下正是赚钱的好时机(前一个状态买进,后一个状态卖出)累计收入O的增加量为下一个状态的股价pn与局部最低点pm的差价,计算差价后局部最低点pm更新为新的下一个状态的股价pn
p n [ i ] = p m [ i ] p_n[i] = p_m[i] pn[i]=pm[i]

O [ i ] = O [ i − 1 ] + p n [ i ] − p m [ i − 1 ] O[i] = O[i-1] + p_n[i]-p_m[i-1] O[i]=O[i1]+pn[i]pm[i1]

我们可以对两种状态转移方程进行合并:
p n [ i ] = p m [ i ] p_n[i] = p_m[i] pn[i]=pm[i]

O [ i ] = O [ i − 1 ] + m a x ( p n [ i ] − p m [ i − 1 ] , 0 ) O[i] = O[i-1] + max(p_n[i]-p_m[i-1],0) O[i]=O[i1]+max(pn[i]pm[i1]0

不难看出,无论股票上涨下跌,pn序列始终和pm序列是保持一致的,所以我们可以对状态转移方程进一步化简,这样一来仅仅通过pn序列(股价序列),就可以推导出收益:

O [ i ] = O [ i − 1 ] + m a x ( p n [ i ] − p n [ i − 1 ] , 0 ) O[i] = O[i-1] + max(p_n[i]-p_n[i-1],0) O[i]=O[i1]+max(pn[i]pn[i1]0

代码实现

基于以上的讨论,我们有两种方法实现代码,分别是状态方程优化前考虑局部最低点的方法,和状态方程优化后仅仅通过股票序列得出收益的方法。

方法1:(状态转移方程优化前:考虑局部最低点的方法)

class Solution(object):
    def maxProfit(self, prices):
        lenp=len(prices)
        if lenp<=1:
            return 0
        minvalue=prices[0]
        dp=0
        for i in range(1,lenp):
            minvalue=min(minvalue,prices[i])
            if prices[i]>=minvalue:
                dp=dp+prices[i]-minvalue
                minvalue=prices[i]
        return dp

方法2:(状态转移方程优化后:仅仅通过股价序列推出最优收益)

class Solution(object):
    def maxProfit(self, prices):
        lenp=len(prices)
        if lenp<=1:
            return 0
        dp=0
        for i in range(1,lenp):
            dp=dp+max(prices[i]-prices[i-1],0)
        return dp

一点小总结

“用最简单的语言,由过去推知未来,这便是动态规划的全部”。这里“最简单的语言”,指的便是状态转移方程。通过状态转移方程,由过去的状态,推知未来的状态,从而达到“道生一,一生二,二生三,三生万物”的效果。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值