好久沒得寫刷題的博客了,正好最近牛市,記錄幾個股票相關的題,其實leetcode上相關內容不少,後面幾道困難的有空再整理進來吧。
121. 買股票的最佳時機 I
給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。
如果你最多隻允許完成一筆交易(即買入和賣出一支股票一次),設計一個算法來計算你所能獲取的最大利潤。
注意:你不能在買入股票前賣出股票。
示例 1:
輸入: [7,1,5,3,6,4]
輸出: 5
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 5 天(股票價格 = 6)的時候賣出,最大利潤 = 6-1 = 5 。
注意利潤不能是 7-1 = 6, 因爲賣出價格需要大於買入價格;同時,你不能在買入前賣出股票。
示例 2:
輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤爲 0。
這道題其實以前有做過,正好筆者最近在玩Go,就嘗試着用Go寫了一遍
package leetcode_121 import ( "math" ) func maxProfit(prices []int) int { var result int = 0 var min int = math.MaxInt64 for i:=0; i < len(prices); i++ { if prices[i] < min { min = prices[i] } else if prices[i] - min > result { result = prices[i] - min } } return result }
其實題目可以簡化成在一個數組中求下標a,b(a<b)使得a,b之間的差值最大.(a點買入股票,b點賣出股票).因此不妨藉助一箇中間變量min,代表股票的最低點,如果我們發現了最低點,那麼就更新買入的時機(只能買賣一次)。否則就判斷當天賣出的利潤是否超過之前的利潤。如此可以在一遍循環內完成計算。
122.買賣股票的最佳時機 II
給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。
設計一個算法來計算你所能獲取的最大利潤。你可以儘可能地完成更多的交易(多次買賣一支股票)。
注意:你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。
示例 1:
輸入: [7,1,5,3,6,4]
輸出: 7
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 3 天(股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
隨後,在第 4 天(股票價格 = 3)的時候買入,在第 5 天(股票價格 = 6)的時候賣出, 這筆交易所能獲得利潤 = 6-3 = 3 。
示例 2:
輸入: [1,2,3,4,5]
輸出: 4
解釋: 在第 1 天(股票價格 = 1)的時候買入,在第 5 天 (股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接連購買股票,之後再將它們賣出。
因爲這樣屬於同時參與了多筆交易,你必須在再次購買前出售掉之前的股票。
示例 3:
輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤爲 0。
提示:
1 <= prices.length <= 3 * 10 ^ 4
0 <= prices[i] <= 10 ^ 4
同樣這道題之前筆者有刷到過,用Go再來一遍:
package leetcode_122 //這麼一想這個思路好像有點意思,只要遇到後面的數值大於前面的,就求差值併入結果中。 func maxProfit(prices []int) int { if len(prices) == 0 { return 0 } var result int = 0 for i:=1; i < len(prices); i++ { if prices[i] - prices[i-1] > 0 { result += prices[i] - prices[i-1] } } return result }
與121的差異在於,這次我們可以買賣多次股票,也就是說即使某次交易不是在最低點開始,也不是在最高點結束,只要他們的差值是正的並且不影響其他的交易操作,我們就應該進行這一次的計算。反應成代碼其實可以說是如果兩個相鄰的數滿足後面的數字較大,我們就可以把他們的差值計算道最後的總收益當中。
可能會對上面的做法產生疑惑,因爲題目有要求不能在同一時間參與多此交易。但對於類似於[4,5,6]這樣的數據,雖然我們的計算過程中"5"參與了兩次計算,但整體來說其實是可以看作一次交易的。因爲我們計算的是每次的差值的和,可以相當於直接計算了4~6這次操作的差值,而“5”只是輔助計算的工具。
309. 最佳買入時機含冷凍期
給定一個整數數組,其中第 i 個元素代表了第 i 天的股票價格 。
設計一個算法計算出最大利潤。在滿足以下約束條件下,你可以儘可能地完成更多的交易(多次買賣一支股票):
- 你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。
- 賣出股票後,你無法在第二天買入股票 (即冷凍期爲 1 天)。
示例:
輸入: [1,2,3,0,2] 輸出: 3 解釋: 對應的交易狀態爲: [買入, 賣出, 冷凍期, 買入, 賣出]
分析題目,筆者第一思路其實是可以嘗試使用動態規劃的思想來解決問題。其實可以把題目轉化成在股票交易週期內每天可以獲得的最大總利潤,而最後的結果就是最後一天的最大利潤。最後一天的最大利潤顯然是受前一天的股票買入賣出狀態所影響的。但再深挖下去,筆者沒有想出一個好的辦法來處理不同的狀態轉移。
(因爲涉及到前一天是否已賣出股票,如果前一天持有股票,則當天可選擇繼續持有或賣出。如果前一天賣出股票,則當天是冷凍期,不能交易。如果前一天沒有股票,則新一天可以選擇不買如或買入股票。)
來看看官方的解法吧:
鏈接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/solution/zui-jia-mai-mai-gu-piao-shi-ji-han-leng-dong-qi-4/
來源:力扣(LeetCode)
方法一:動態規劃
思路與算法
我們用 f[i]f[i] 表示第 ii 天結束之後的「累計最大收益」。根據題目描述,由於我們最多隻能同時買入(持有)一支股票,並且賣出股票後有冷凍期的限制,因此我們會有三種不同的狀態:
我們目前持有一支股票,對應的「累計最大收益」記爲 f[i][0]f[i][0];
我們目前不持有任何股票,並且處於冷凍期中,對應的「累計最大收益」記爲 f[i][1]f[i][1];
我們目前不持有任何股票,並且不處於冷凍期中,對應的「累計最大收益」記爲 f[i][2]f[i][2]。
這裏的「處於冷凍期」指的是在第 ii 天結束之後的狀態。也就是說:如果第 ii 天結束之後處於冷凍期,那麼第 i+1i+1 天無法買入股票。
如何進行狀態轉移呢?在第 ii 天時,我們可以在不違反規則的前提下進行「買入」或者「賣出」操作,此時第 ii 天的狀態會從第 i-1i−1 天的狀態轉移而來;我們也可以不進行任何操作,此時第 ii 天的狀態就等同於第 i-1i−1 天的狀態。那麼我們分別對這三種狀態進行分析:
對於 f[i][0]f[i][0],我們目前持有的這一支股票可以是在第 i-1i−1 天就已經持有的,對應的狀態爲 f[i-1][0]f[i−1][0];或者是第 ii 天買入的,那麼第 i-1i−1 天就不能持有股票並且不處於冷凍期中,對應的狀態爲 f[i-1][2]f[i−1][2] 加上買入股票的負收益 {\it prices}[i]prices[i]。因此狀態轉移方程爲:
f[i][0] = \max(f[i-1][0], f[i-1][2] - {\it prices}[i])
f[i][0]=max(f[i−1][0],f[i−1][2]−prices[i])
對於 f[i][1]f[i][1],我們在第 ii 天結束之後處於冷凍期的原因是在當天賣出了股票,那麼說明在第 i-1i−1 天時我們必須持有一支股票,對應的狀態爲 f[i-1][0]f[i−1][0] 加上賣出股票的正收益 {\it prices}[i]prices[i]。因此狀態轉移方程爲:
f[i][1] = f[i-1][0] + {\it prices}[i]
f[i][1]=f[i−1][0]+prices[i]
對於 f[i][2]f[i][2],我們在第 ii 天結束之後不持有任何股票並且不處於冷凍期,說明當天沒有進行任何操作,即第 i-1i−1 天時不持有任何股票:如果處於冷凍期,對應的狀態爲 f[i-1][1]f[i−1][1];如果不處於冷凍期,對應的狀態爲 f[i-1][2]f[i−1][2]。因此狀態轉移方程爲:
f[i][2] = \max(f[i-1][1], f[i-1][2])
f[i][2]=max(f[i−1][1],f[i−1][2])
這樣我們就得到了所有的狀態轉移方程。如果一共有 nn 天,那麼最終的答案即爲:
\max(f[n-1][0], f[n-1][1], f[n-1][2])
max(f[n−1][0],f[n−1][1],f[n−1][2])
注意到如果在最後一天(第 n-1n−1 天)結束之後,手上仍然持有股票,那麼顯然是沒有任何意義的。因此更加精確地,最終的答案實際上是 f[n-1][1]f[n−1][1] 和 f[n-1][2]f[n−1][2] 中的較大值,即:
\max(f[n-1][1], f[n-1][2])
max(f[n−1][1],f[n−1][2])
細節
我們可以將第 00 天的情況作爲動態規劃中的邊界條件:
\begin{cases} f[0][0] &= -{\it prices}[0] \\ f[0][1] &= 0 \\ f[0][2] &= 0 \end{cases}
⎩
⎪
⎨
⎪
⎧
f[0][0]
f[0][1]
f[0][2]
=−prices[0]
=0
=0
在第 00 天時,如果持有股票,那麼只能是在第 00 天買入的,對應負收益 -{\it prices}[0]−prices[0];如果不持有股票,那麼收益爲零。注意到第 00 天實際上是不存在處於冷凍期的情況的,但我們仍然可以將對應的狀態 f[0][1]f[0][1] 置爲零,這其中的原因留給讀者進行思考。
這樣我們就可以從第 11 天開始,根據上面的狀態轉移方程進行進行動態規劃,直到計算出第 n-1n−1 天的結果。
C#實現:
public class Solution { public int MaxProfit(int[] prices) { if (prices.Length == 0) return 0; int[,] dp = new int[prices.Length,3]; dp[0, 0] = -prices[0]; for (int i = 1; i < prices.Length; i++) { dp[i, 0] = Math.Max(dp[i - 1, 0], dp[i - 1, 2] - prices[i]); dp[i, 1] = dp[i - 1, 0] + prices[i]; dp[i, 2] = Math.Max(dp[i - 1, 1], dp[i - 1, 2]); } return Math.Max(dp[prices.Length - 1, 1], dp[prices.Length - 1, 2]); } }
Go實現:
func maxProfit(prices []int) int { if len(prices) == 0 { return 0 } var dp0 = -prices[0] var dp1 = 0 var dp2 = 0 for i:= 1; i<len(prices); i++{ var newdp0 int if dp0 > (dp2 - prices[i]){ newdp0 = dp0 } else { newdp0 = dp2 - prices[i] } newdp1 := dp0 + prices[i] var newdp2 int if dp1 > dp2{ newdp2 = dp1 } else { newdp2 = dp2 } dp0 = newdp0 dp1 = newdp1 dp2 = newdp2 } if dp1 > dp2 { return dp1 } else { return dp2 } }