dp基础之序列型Stock

问题:已知后N天 的股票价格为p[0],p[1]..p[N-1](N>=2)
要求:可以最多买一股卖一股,
求最大获利

分析:
    枚举第j天卖(0<j<=N-1)
    保存j天之前第i天卖的最小值(i<j)
    返回最大的利润(j-i)

 

代码及注释如下:

def get_proft(p):
    n = len(p)
    #初始化
    #min_j时刻保存在第j天卖出之前的最小值,也就是0,1,...,j-2,里的最小值
    min_j = p[0]
    #por_j[j]表示在天j(不是第j天)卖出时的最大利润
    pro_j = [0 for i in range(n)]
    #1天卖出时只有0天买入
    pro_j[1] = p[1]-p[0]
    for j in range(2,n):
        #如果天j之前也就是天j-1,比之前0,,1,...,j-2里的最小值还小,则更新最小值,并记录天j卖出时的最大利润
        if p[j-1] < min_j:
            min_j = p[j-1]
            pro_j[j] = p[j] - min_j
        #否则,天j之前的最小值不更新,同时记录j天卖出时的最大利润
        else:
            pro_j[j] = p[j]-min_j
    return max(pro_j)
    

#p[j]表示天j的利润
p = [3,1,2,19,0,6,16]
print(get_proft(p))
答案:18

时间复杂度为O(N),空间复杂度为O(1),上述代码可以只用一个min_pro即可,不用list

 

问题:若将上述问题改为:可以卖一股任意多次,但是任意时刻手中只有一股,求最大利润。

代码及注释如下:

def get_nproft(p):
    res = 0
    for i in range(1,len(p)):
        #后一天的价格比前一天高,即上升,就买
        if p[i] -p[i-1] > 0:
            res += p[i] -p[i-1]
    return res
p = [2,1,2,0,1]
print(get_nproft(p))
#答案:2

问题:若将原问题改为:可以最多买卖两次,每次买卖只能一股,且不能在卖光手中股票前买入,但可以在同一天卖完后买入,
求最大利润。

p = [4,4,6,1,1,4,2,5]

 

分析:
确定状态:(假设第N-1天时今天,第N-2天就是昨天)
最后一次卖在第j天,枚举最后一次买在第i天(i<j),但是不知道在第i天之前有没有买卖过


最优策略一定是前N天,第N-1天结束后处于:

阶段1:没有买卖过
阶段3:买卖过1次
阶段5:买卖过2次

例如:

如图示意:


如果要求前N天(第N-1天)结束后,在阶段5的最大获利,设为f[N][5]:
情况1:第N-2天就在阶段5,利润对应为f[N-1][5]
情况2:第N-2天还在阶段4,即处于第二次持有股票,在第N-1天卖掉,则利润对应为  f[N-1][4]  +   (p[N-1]-p[N-2])

如果要求前N天(第N-1天)结束后,在阶段4的最大获利,设为f[N][4]:
情况1:第N-2天就在阶段4,利润对应为f[N-1][4] + (p[N-1]-p[N-2])
情况2:第N-2天还在阶段3,即第N-1天刚买入,并没有获利,
    则利润还是对应为第N-2天(第N-2天是处于阶段3的)的利润: f[N-1][3]
情况3:第N-2还在阶段2,第N-1天卖完立即买入,即第N-1天结束后处于第二次持有股票,没有阶段3,直接进入阶段4
    对应利润为 f[N-1][2] + (p[N-1]-p[N-2]),

子问题:
要求f[N][1]...f[N][5],需要知道f[N-1][1]...f[N-1][5]
状态:f[i][j]表示前i天(第i天)结束后,在阶段j的最大获利
在阶段1,3,5,手中无股票的状态:
        f[i][j] = max{ f[i-1][j],        f[i-1][j-1] + p[i-1]-p[i-2] }
                    {昨天没有持股票;   昨天持有股票且今天卖出股票清仓}

在阶段2,4,手中持有股票:
        f[i][j] = max{ f[i-1][j] + p[i-1] - p[i-2] ,       f[i-1][j-1] , f[i-1][j-2] + p[i-1]-p[i-2] }
{昨天持有股票且今天继续持有并获利; 昨天没有股票且今天刚买入获利明天算; 昨天持有上一次的股票今天卖出并立即买入} 

边界条件和初始情况:
刚开始(前0天)处于阶段1
    f[0][1] = 0    f[0][2] = f[0][3] = f[0][4] f[0][5] = -sys.maxsize(因为本体要求最大值,故初始为负无穷)
    
    阶段1,3,5:
    f[i][j] = max{ f[i-1][j],f[i-1][j-1] + p[i-1]-p[i-2] }
    阶段2,4
    f[i][j] = max{ f[i-1][j] + p[i-1] - p[i-2] ,f[i-1][j-1],f[i-1][j-2] + p[i-1]-p[i-2] }

如果 j-1<1 或者 i-2<0 则对应项不计入max,因为是无意义的
因为最多买卖2次,故答案是max{f[N][1],f[N][3],f[N][5]},必须处于清仓状态下最后一天的最大获利


计算顺序:
f[0][1]...f[0][5]
f[1][1]...f[1][5]
.
.
.
f[N][1]...f[N][5]

时间复杂度是O(n),空间复杂度为O(n),但可以优化到O(1),因为f[i][1]...f[i][5]只依赖于f[i-1][1]...f[i-1][5]

 

代码及注释如下:

def get_2_stock(p):
    n = len(p)
    if n == 0:
        return 0
    #创建一个(n+1)*(5+1)二维列表f[i][j],f[i][j]表示前i天(第i天 )结束后,在阶段j的最大获利
    f = [[0 for i in range(5+1)] for j in range(n+1)]
    #初始化,前0天处于阶段1的最大获利为0
    f[0][1] = 0
    #f[0][2]=f[0][3]=f[0][4]=f[0][5] = -sys.maxsize
    for j in range(2,6):
        f[0][j] = -sys.maxsize
    
    for i in range(1,n+1):
        #前i天处于阶段1,3,5
        for j in range(1,6,2):
            #f[i][j] = max{ f[i-1][j],        f[i-1][j-1] + p[i-1]-p[i-2] }
            f[i][j] = f[i-1][j]
            if j > 1 and i > 1 and f[i-1][j-1] != -sys.maxsize:
                f[i][j] = max(f[i-1][j],f[i-1][j-1] + p[i-1]-p[i-2])
            
        #前i天处于阶段2,4
        for j in range(2,5,2):
            #f[i][j] = max{ f[i-1][j] + p[i-1] - p[i-2] ,f[i-1][j-1],f[i-1][j-2] + p[i-1]-p[i-2] }
            f[i][j] = f[i-1][j-1]
            if i > 1 and f[i-1][j] != -sys.maxsize:
                f[i][j] = max(f[i][j],f[i-1][j] + p[i-1]-p[i-2])
            if j > 2 and i > 1 and f[i-1][j-2] != -sys.maxsize:
                f[i][j] = max(f[i][j],f[i-1][j-2] + p[i-1]-p[i-2])
    return max(f[n][1],f[n][3],f[n][5])
            
                
p = [4,4,6,1,1,4,2,5]
print(get_2_stock(p))

#结果:6
    

 

问题:若将原问题改为:可以最多买卖K次,每次买卖只能一股,且不能在卖光手中股票前买入,但可以在同一天卖完后买入,
求最大利润。

 

分析:
如果K很大,K>N/2,则题目可以简化成任意次买卖,因为两天一次买卖,要是两天进行3次买卖则这3次买卖中有一次买卖是没有意义的。

现在来分析K<=N/2的情况:上问题中详细讲了2次的情况,现在只要把2次推广到K次即可:

如下图示意:

f[i][j]表示前i天(第i天)结束后,在阶段j的最大获利
a:j=1,3,...,2K+1,即奇数阶段手中无股时:
            f[i][j] = max{ f[i-1][j],        f[i-1][j-1] + p[i-1]-p[i-2] }
                    {昨天没有持股票;   昨天持有股票且今天卖出股票清仓}
    
b:j=2,4,...,2K,即偶数阶段手中有股时:
        f[i][j] = max{ f[i-1][j] + p[i-1] - p[i-2] ,       f[i-1][j-1] , f[i-1][j-2] + p[i-1]-p[i-2] }
{昨天持有股票且今天继续持有并获利; 昨天没有股票且今天刚买入获利明天算; 昨天持有上一次的股票今天卖出并立即买入} 
    
  
边界条件和初始情况:
刚开始(前0天)处于阶段1
    f[0][1] = 0    f[0][2] = f[0][3] = ... = f[0][2K] = f[0][2K+1] = -sys.maxsize(因为本体要求最大值,故初始为负无穷)

如果 j-1<1 或者 i-2<0 则对应项不计入max,因为是无意义的
因为最多买卖K次,故答案是max{f[N][1],f[N][3],...,f[N][2K+1]},必须处于清仓状态下最后一天的最大获利

计算顺序:
f[0][1]...f[0][2K+1]
f[1][1]...f[1][2K+1]
.
.
.
f[N][1]...f[N][2K+1]

时间复杂度是O(nk),空间复杂度为O(nk),但可以优化到O(k),因为f[i][1]...f[i][2K+1]只依赖于f[i-1][1]...f[i-1][2K+1]

代码及注释如下:

def get_K_stock(p,K):
    n = len(p)
    if n == 0:
        return 0
    #如果K>n/2,直接转化成买卖任意次
    if K>n/2:
        res = 0
        for i in range(1,n):
            #后一天的价格比前一天高,即上升,就买
            if p[i] -p[i-1] > 0:
                res += p[i] -p[i-1]
        return res
    
    #创建一个(n+1)*(2*K+1+1)二维列表f[i][j],f[i][j]表示前i天(第i天 )结束后,在阶段j的最大获利
    f = [[0 for i in range((2*K+1)+1)] for j in range(n+1)]
    #初始化,前0天处于阶段1的最大获利为0
    f[0][1] = 0
    #f[0][2]=f[0][3]=...=f[0][2*K]=f[0][2*K+1] = -sys.maxsize
    for j in range(2,(2*K+1)+1):
        f[0][j] = -sys.maxsize
    
    for i in range(1,n+1):
        #前i天处于阶段1,3,5
        for j in range(1,(2*K+1)+1,2):
            #f[i][j] = max{ f[i-1][j],        f[i-1][j-1] + p[i-1]-p[i-2] }
            f[i][j] = f[i-1][j]
            if j > 1 and i > 1 and f[i-1][j-1] != -sys.maxsize:
                f[i][j] = max(f[i-1][j],f[i-1][j-1] + p[i-1]-p[i-2])
            
        #前i天处于阶段2,4
        for j in range(2,2*K+1,2):
            #f[i][j] = max{ f[i-1][j] + p[i-1] - p[i-2] ,f[i-1][j-1],f[i-1][j-2] + p[i-1]-p[i-2] }
            f[i][j] = f[i-1][j-1]
            if i > 1 and f[i-1][j] != -sys.maxsize:
                f[i][j] = max(f[i][j],f[i-1][j] + p[i-1]-p[i-2])
            if j > 2 and i > 1 and f[i-1][j-2] != -sys.maxsize:
                f[i][j] = max(f[i][j],f[i-1][j-2] + p[i-1]-p[i-2])
    
    #返回f[n][1],f[n][3],...,f[n][2*K+1]的最大值
    return max(f[n][1::2])
            
                
p = [4,4,6,1,1,4,2,5]
K = 2
print(get_K_stock(p,K))

#结果:6
    

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值