由计算股票最长增长期引出的————连续最大子序列和的算法(python)

引出问题——如何计算一只股票的最长增长期?

 首先声明本博客是结合吴军老师的<谷歌方法论>以及自己的一些想法,用python来直观的感受一下,随着数量级的增加,优秀的算法能带来什么样的效果!!!

股票有效增长期

如果你经常玩股票,那么就不难理解什么是有效增长期,所谓有效增长,就是一支股票比大盘涨的快的那部分,事实上,当有一支股票的价格涨速超过股指时,购买和持有它才有意义,因此,我们要扣除掉整个市场对股票价格的影响,当一支股票每天上涨的速度超过股指指数,我们就认为它的有效增长是正数,否则就是负数

如上图:其中每个数字代表它今天比昨天的增长减去股指指数增长后的净值,单位是基本点,也就是万分之一,这只股票在这13天的内比大盘累计有效增长为27.7个基本点,但是这支股票的最长增长期并不是1-13天,而是5-10天,也就是说,你如果在第5天买入,在第10天卖出,那么你的累计有效增长就比股指高出52.4个基本点。由此可以引出在面试中经常出现的一道面试题——在一个序列中求连续最大子序列和。

五种方法计算连续最大子序列和

方法一:做三重循环——时间复杂度O=n^3

这种方法又称为暴力算法,即求出每个子序列的和,找出最大和

from random import randrange
import time


#随机生成一组列表
def make_list(start,stop,lenth):
    nums = []
    while lenth:
        nums.append(randrange(start,stop,1))
        lenth -= 1
    return nums

nums = make_list(-100,100,1000)


#方法一
def maxsum1(nums):
    maxsum = nums[0]
    print(len(nums))
    for i in range(len(nums)):#i为子序列起始位置
        j = i
        for j in range(i,len(nums)):#j为子序列的结束位置
            s = 0
            k = j
            for k in range(i,j+1):#遍历子序列求和
                s += nums[k]
            if s > maxsum:
                maxsum = s
    return maxsum


s1 = time.time()
value = maxsum1(nums)
s2 = time.time()
print("方法一:",s2-s1)
print("最大连续子序列和:",value)

方法二:做两层循环,简单优化  时间复杂度O(N^2)

按照上面的算法,计算子序列[Ai...A[j]]的和,我们需要遍历子序列的每个元素,其实我们在计算[Ai...A[j]的和之前,已经
计算了[Ai...A[j-1]]的和,因此只需计算sum(A[i]...A[j-1]]+A[j]即可得到子序列[Ai...A[j]]的和即可

#继续沿用上面产生的随机列表nums
def maxsum2(nums):
    maxsum = nums[0]
    for i in range(len(nums)):#i为子序列起始位置
        j = i
        s = 0
        for j in range(i,len(nums)):#j为子序列的结束位置
            s += nums[j]
            if s > maxsum:
                maxsum = s
    return maxsum

s1 = time.time()
value = maxsum2(nums)
s2 = time.time()
print("方法二:",s2-s1)
print("最大连续子序列和:",value)

方法三:分治算法 时间复杂度O(NlogN)

将序列平均分为左右两个子序列,存在下面三种情况:
1.要求的连续子序列在左序列中
2.要求的连续子序列在右序列中
3.要求的连续子序列刚好横跨分割点,即左右序列各占一部分
对于第1、2种情况:分别利用递归求出连续子序列的最大和S1、S2,
对于第3情况:最大子序列和由左序列最大和(包含左序列最后一个元素)
              右序列最大和(包含右序列第一个元素)构成,
              将两个和相加得到S3 
那么要求的最大子序列和为这三个数S1,S2,S3的最大者
例如:
       左序列 |  右序列
   4 -3  5 -2 | -1 2 6 -2
 左序列最大子序列和为6(A1到A3) 
 右序列最大子序列和为8(A6到A7)
 横跨分割点的最大子序列的和为11:
    即左序列包含最后一个元素最大和4(A1到A4),右序列包含第一个元素最大和7(A5到A7)
    4+7=11

def maxsum3(nums):
    if len(nums) == 1:
        return nums[0]
    #分组
    center = len(nums)//2
    left_nums = nums[0:center]
    right_nums = nums[center:len(nums)]
    
    #分别求左右序列最大子序列和
    left_maxsum = maxsum3(left_nums)
    right_maxsum = maxsum3(right_nums)
   
    #求左序列最大和(包括最后一个元素)
    left_sum = 0
    left_max= left_nums[len(left_nums)-1]
    i = len(left_nums)-1
    while i >= 0:
        left_sum += left_nums[i]
        
        if left_sum > left_max:
            left_max = left_sum
        i -= 1
    
    #求右序列最大和(包括第一个元素)
    right_sum =0 
    right_max = right_nums[0]
    i = 0
    while i < len(right_nums):
        right_sum += right_nums[i]
        if right_sum > right_max:
            right_max = right_sum
        i += 1
        
    l = [left_maxsum,right_maxsum,left_max + right_max]
    return max(l)

s1 = time.time()
value = maxsum3(nums)
s2 = time.time()
print("方法三:",s2-s1)
print("最大连续子序列和:",value)

方法四:动态规划 时间复杂度O(N)

分析
步骤1:
令dp[i]表示以A[i]作为结尾的连续子序列的最大和
步骤2:
因为dp[i]要求必须以A[i]结尾的连续序列,那么只有两种情况:
    1.这个最大连续序列只有一个元素,即以A[i]开始,以A[i]结尾
    2.这个最大和的连续序列有多个元素,即以A[p]开始(p<i),以A[i]结尾
对于情况1,最大和就是A[i]本身
对于情况2,最大和是dp[i-1]+A[i]
于是得到状态转移方程:
dp[i]=max{A[i],dp[i-1]+A[i]} 
步骤3:
连续子序列的和为
maxsub[n]=max{dp[i]} (1<=i<=n)

#方法四
def maxsum4(nums):
    if len(nums) == 1:
        return nums[0]
    dp = res = nums[0]
    
    for i in range(1,len(nums)):
        dp = max(nums[i],dp + nums[i])
        res = max(dp,res)
    return res


s1 = time.time()
value = maxsum4(nums)
s2 = time.time()
print("方法四:",s2-s1)
print("最大连续子序列和:",value)

方法五:对第二种方法进一步优化

方法二已经对方法一进行了优化,由之前的三重循环变为两层循环,不过两层循环对于较大数量级的计算还是力不从心,比如数据达到百万时,二层循环给计算机带来的计算量就是万亿级别的,这对于普通计算机来说,已经是很大负荷的计算量了

其实我们可以将第二种方法进行改进,只用一层循环就可以解决问题,当然第四种方法也只用了一层循环,不过做比较的次数太多,在后面我们会对几种方法的计算速度进行比较,发现第四种算法虽然和第五种算法的时间复杂度都是n,但是第五种算法还是比第四种算法快一倍,下面我们具体看下第五种算法!

#方法五:
def maxsum5(nums):
    maxsum = 0
    s = 0
    for i in range(len(nums)):
        s += nums[i]#向右累加
        if s > maxsum:
            maxsum = s
        elif s <= 0:
            #如果累加和出现负值或零,那么代表前面的元素一定不在增长期内
            #将累加和清零,从当前位置以起始位置重新计算
            s = 0
    return maxsum


s1 = time.time()
value = maxsum5(nums)
s2 = time.time()
print("方法五:",s2-s1)
print("最大连续子序列和:",value)

五种方法运行时间的比较

对以上五种方法,我分别将列表生成不同的量级来,来直观的感受下不同的算法会达到怎样的运算效果!

列表长度为100时的计算结果

列表为1000时的计算结果,此时方法一已经力不从心了,结算结果太慢,下面的计算中我们将第一种方法淘汰

列表长度为10000时的计算,方法二的两层循环也很吃力了

列表长度为100000时,方法三,四,五都还可以轻松应对

列表长度为1000000时,最后三种方法都可以应对,但是可以明显看出来,方法三已经处于劣势了,方法四和方法五,依旧可以轻松应对

列表长度为10电脑000000时,最后三种方法的优劣已经清晰的展现出来了

对于上亿级别的数量级我就不展示了,电脑配置有限,我把整段代码放在下面,有兴趣的网友,可以自己试一下!

from random import randrange
import time

#方法一
def maxsum1(nums):
    maxsum = nums[0]
    for i in range(len(nums)):#i为子序列起始位置
        j = i
        for j in range(i,len(nums)):#j为子序列的结束位置
            s = 0
            k = j
            for k in range(i,j+1):#遍历子序列求和
                s += nums[k]
            if s > maxsum:
                maxsum = s
    return maxsum

#方法二
def maxsum2(nums):
    maxsum = nums[0]
    #print(len(nums))
    for i in range(len(nums)):#i为子序列起始位置
        j = i
        s = 0
        for j in range(i,len(nums)):#j为子序列的结束位置
            s += nums[j]
            #print(s)
            if s > maxsum:
                maxsum = s
    return maxsum
    
#方法三   
def maxsum3(nums):
    if len(nums) == 1:
        return nums[0]
    #分组
    center = len(nums)//2
    left_nums = nums[0:center]
    right_nums = nums[center:len(nums)]
    
    #分别求左右序列最大子序列和
    left_maxsum = maxsum3(left_nums)
    right_maxsum = maxsum3(right_nums)
   
    #求左序列最大和(包括最后一个元素)
    left_sum = 0
    left_max= left_nums[len(left_nums)-1]
    i = len(left_nums)-1
    while i >= 0:
        left_sum += left_nums[i]
        
        if left_sum > left_max:
            left_max = left_sum
        i -= 1
    
    #求右序列最大和(包括第一个元素)
    right_sum =0 
    right_max = right_nums[0]
    i = 0
    while i < len(right_nums):
        right_sum += right_nums[i]
        if right_sum > right_max:
            right_max = right_sum
        i += 1
        
    l = [left_maxsum,right_maxsum,left_max + right_max]
    return max(l)
 
#方法四
def maxsum4(nums):
    if len(nums) == 1:
        return nums[0]
    dp = res = nums[0]
    
    for i in range(1,len(nums)):
        dp = max(nums[i],dp + nums[i])
        res = max(dp,res)
    return res 


#方法五:
def maxsum5(nums):
    maxsum = 0
    s = 0
    for i in range(len(nums)):
        s += nums[i]#向右累加
        if s > maxsum:
            maxsum = s
        elif s <= 0:
            #如果累加和出现负值或零,那么代表前面的元素一定不在增长期内
            #将累加和清零,从当前位置以起始位置重新计算
            s = 0
    return maxsum


def make_list(start,stop,lenth):
    nums = []
    while lenth:
        nums.append(randrange(start,stop,1))
        lenth -= 1
    return nums

nums = make_list(-100,100,10000) 
print("生成的随机列表长度为:",len(nums))   


s1 = time.time()
value = maxsum1(nums)
s2 = time.time()
print("方法一:",s2-s1)
print("最大连续子序列和:",value)

s1 = time.time()
value = maxsum2(nums)
s2 = time.time()
print("方法二:",s2-s1)
print("最大连续子序列和:",value)

s1 = time.time()
value = maxsum3(nums)
s2 = time.time()
print("方法三:",s2-s1)
print("最大连续子序列和:",value)

s1 = time.time()
value = maxsum4(nums)
s2 = time.time()
print("方法四:",s2-s1)
print("最大连续子序列和:",value)

s1 = time.time()
value = maxsum5(nums)
s2 = time.time()
print("方法五:",s2-s1)
print("最大连续子序列和:",value)

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值