问题:已知后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