引出问题——如何计算一只股票的最长增长期?
首先声明本博客是结合吴军老师的<谷歌方法论>以及自己的一些想法,用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)