dp基礎之序列型Stock
問題:已知後N天 的股票價格為p[0]...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天賣出之前的最小值 min_j = p[0] #por_j[j]表示在天j(不是第j天)賣出時的最大利潤 pro_j = [0 for i in range(n)] #1天賣出時只有0天買入 pro_j[1] = p[1]-min_j for j in range(2,n): #如果天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