大家好,今天我們來聊一聊股票交易問題。
Tips:炒股投資的朋友可以直接走了,這是程式設計師的筆試面試題,不是真正的去探討炒股哦!不過這兩天港股漲的不錯...
前幾天群裡的小夥伴參加位元組面試,遇到了股票交易這麼一道題。今天我們就來分析一下。同時也給即將要參加校招的朋友們提供準備,這是位元組騰訊等大廠校招時常考的題目。
問題描述:
給定一個陣列prices,它的第i個元素 prices[i]表示一支給定股票第i天的價格。你只能選擇某一天買入這隻股票,並選擇在未來的某一個不同的日子賣出該股票。設計一個演算法來計算你所能獲取的最大利潤。返回你可以從這筆交易中獲取的最大利潤。如果你不能獲取任何利潤,返回 0。
分析問題:
拿到這個問題,我們就需要先思考用什麼樣的思想去解決。我們來看這個題目,這個問題是求最優解問題。初看題目,我們最容易想到的方法,就是暴力求解,即遍歷陣列,求出最大值和最小值,相減就是最大利潤。不過這裡有一個隱含條件,就是股票的賣出時機必須大於股票的買入時機。
def maxProfit(prices):
#最大利潤
ans = 0
#股票賣出時機要大於股票買入時機
for i in range(len(prices)):
for j in range(i + 1, len(prices)):
ans = max(ans, prices[j] - prices[i])
return ans
prices=[7, 1, 5, 3, 6, 4]
maxProfit(prices)
我們可以看出,這種求解的方法,時間複雜度是O(n^2),那有沒有時間複雜度更優的方法呢?
首先,我們來分析一下這個問題符合什麼“問題模型”呢?我們從0開始走,走到n-1,一共需要走n-1步,也就對應著n-1個階段。每個階段都只能往右走,並且每個階段都會對應一個狀態集合,我們把狀態定義為maxprofit[i],其中i表示走到哪一步,maxprofit[i]的值表示從開始走到位置i的最大利潤是多少。所以這個問題是“多階段決策最優”問題,對於這類問題,我們首先要考慮能否用動態規劃的思想來解決,也就是看是否符合動態規劃的特徵:重複子問題、無後效性、最優子結構。
-
無後效性:我們要想計算maxprofit[i]這個狀態,只需要關心maxprofit[i-1]的狀態,並不關心maxprofit[i-1]這個狀態是如何生成的。而且,我們只能往後移動,不允許後退。所以,前面階段的狀態確定之後,不會被後面階段的決策所改變。所以符合"無後效性"。
-
重複子問題:如果要達到maxprofit[i]這個狀態,我們可以有不同的決策序列。比如假設第五天,我們的最大利潤是8,即maxprofit[i]的狀態為8,我們可以是第一天買入2,第三天賣出10。也可以第二天買入2,第三天賣出10。
-
最優子結構:因為maxprofit[i]可以通過maxprofit[i-1]推匯出來,即符合“最優子結構”
maxprofit[i]=max(maxprofit[i-1],prices[i]-minprice)
下面我們看程式碼如何實現:
def maxProfit(prices):
n=len(prices)
if n==0:
return 0
maxPRofit=[0]*n
minprice=prices[0]
for i in range(1,n):
minprice = min(minprice, prices[i])
maxPRofit[i]=max(maxPRofit[i-1],prices[i]-minprice)
return maxPRofit[-1]
prices=[7, 1, 5, 3, 6, 4]
print(maxProfit(prices))
我們可以看出,這種求解的時間複雜度為O(n)。
擴充套件:
我們再把問題複雜一點,假如我們可以進行多次交易一支股票。
給定一個陣列 prices,其中 prices[i] 是一支給定股票第 i 天的價格。設計一個演算法來計算你所能獲取的最大利潤。你可以儘可能地完成更多的交易(多次買賣一支股票)。注意:你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。
這個問題的解題思路和上題類似,也是“多階段決策最優”問題。也符合動態規劃解題模型。不過這裡的“狀態”會有所區別,我們來看一下。
考慮到“不能同時參與多筆交易”。所以,每天交易結束後手裡只可能存在有股票和沒有股票兩種狀態。所以,我們可以用二個陣列來表示,其中dp1[i]表示第i天交易後,手裡沒有股票的最大利潤。dp2[i]表示第i天交易完成後,手裡有股票的最大利潤。
dp1[i]表示第i天手裡沒有股票的最大利潤,那麼這個狀態要麼是由dp1[i-1],即前一天手裡沒有股票的最大利潤,轉移過來。要麼由dp2[i-1]+prices[i],即前一天手裡有股票,今天把這個股票賣出。所以dp1[i]的狀態轉移方程為:
dp1[i]=max(dp1[i-1],dp2[i-1]+prices[i])
dp2[i]表示第i天手裡有股票的最大利潤,那麼這個狀態要麼是由dp2[i-1],即前一天手裡有股票的最大利潤,轉移過來。要麼由dp1[i-1]-prices[i],即前一天手裡沒有股票,今天把這個股票進行買入。所以dp2[i]的狀態轉移方程為:
dp2[i]=max(dp2[i-1],dp1[i-1]-prices[i])
所以我們程式碼實現如下所示:
def maxProfit(prices):
n=len(prices)
if n==0:
return 0
dp1=[0]*n
dp2=[0]*n
#手裡沒有股票
dp1[0]=0
#手裡有股票
dp2[0]=-prices[0]
for i in range(1,n):
dp1[i]=max(dp1[i-1],dp2[i-1]+prices[i])
dp2[i]=max(dp2[i-1],dp1[i-1]-prices[i])
return dp1[n-1]
prices=[7, 1, 5, 3, 6, 4]
print(maxProfit(prices))
我們再來把題目升級一下,即我們限制股票買賣的次數,比如我們只能交易兩次。
給定一個陣列,它的第 i 個元素是一支給定的股票在第 i 天的價格。設計一個演算法來計算你所能獲取的最大利潤。你最多可以完成兩筆交易。
注意:你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。
這個題目和上個題相比,狀態又發生了變化,這裡因為限制了交易次數,所以我們需要把交易次數的狀態也儲存起來。所以每天交易結束後,有5種狀態。
- 未進行過任何操作。
- 只進行過一次買入操作。
- 進行過一次買入和賣出操作。
- 完成一筆的交易前提下,進行了第二次買入操作。
- 完成兩筆交易。
由於第一個狀態的利潤為0,所以我們不需要記錄。我們把剩下的4個狀態分別用buy1,sell1,buy2,sell2來表示。下面我們來看一下如何根據前一天的狀態,來通過轉移方程生成今天的4個狀態。
對於buy1而言,我們可以今天不操作,直接通過昨天的buy′ 1轉移過來,或者我們從未進行過任何操作前提下,今天買入股票。
buy1 =max(buy′ 1,-prices[i])
對於sell1而言,我們今天可以不做任何操作,直接通過昨天的sell′ 1轉移過來,或者我們可以在只進行過一次買入交易的前提下,今天把股票賣出。
sell1=max(sell′ 1,buy′ 1+prices[i])
對於buy2而言,我們今天可以不做任何操作,直接通過昨天的buy′ 2轉移過來,或者,我們在進行過一筆完成的交易條件下,今天再把該股票買入。
buy2=max(buy′ 2,sell′ 1-prices[i])
對於sell2而言,我們今天可以不做任何操作,直接通過昨天的sell′ 2 轉移過來,或者,我們在buy2的前提下,今天把股票賣出。即
sell2=max(sell′ 2,buy′ 2+prices[i])
Tips:我們在計算sell1時,我們可以直接使用buy1而不是buy′ 1進行轉移。buy1比buy′ 1多考慮的是在第i天買入股票的情況,而轉移到sell1時,考慮的是在第i天賣出股票的情況,這樣在同一天買入並且賣出股票的收益為0,不會對結果產生影響。對於buy2和sell2也是同樣的考慮,我們可以直接用第i天求出的值來進行轉移。
buy1=max(buy1,-prices[i])
sell1=max(sell1,buy1+prices[i]
buy2=max(buy2,sell1-price[i])
sell2=max(sell2,buy2+prices[i])
我們來看程式碼實現:
def maxProfit(prices):
n = len(prices)
#表示以prices[0]買入股票
buy1=-prices[0]
#表示在同一天買入並且賣出,即為0
sell1=0
#表示同一天買入賣出後再以prices[0]的價格買入
buy2=-prices[0]
#表示同一天買入賣出兩次,即為0
sell2 = 0
for i in range(1, n):
buy1 = max(buy1, -prices[i])
sell1 = max(sell1, buy1 + prices[i])
buy2 = max(buy2, sell1 - prices[i])
sell2 = max(sell2, buy2 + prices[i])
return sell2
prices=[7, 1, 5, 3, 6, 4]
print(maxProfit(prices))
最後
對於像“多階段決策最優”問題來說,我們首先要考慮是否能用動態規劃來解決,看是否滿足動態規劃的三個特徵:重複子問題、最優子結構、無後效性。然後寫出狀態轉移方程,那問題基本就解決了。
今天,我們就聊到這裡。更多有趣知識,請關注公眾號【程式設計師學長】。
你知道的越多,你的思維也就越開闊,我們下期再見。