1. 題目描述
LeetCode 122.買賣股票的最佳時機 II
在每一天,你可以決定是否購買和/或出售股票。你在任何時候最多隻能持有一股股票。你也可以先購買,然後在同一天出售。返回你能獲得的最大利潤 。
示例 :
*輸入:prices = [7,1,5,3,6,4]
輸出:7
解釋:在第 2 天(股票價格 = 1)的時候買入,在第 3 天(股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5 - 1 = 4 。
隨後,在第 4 天(股票價格 = 3)的時候買入,在第 5 天(股票價格 = 6)的時候賣出, 這筆交易所能獲得利潤 = 6 - 3 = 3 。
總利潤為 4 + 3 = 7 。*
這道題常見且並不難,有意思的是解法也非常多,尤其適合入門級別的單調棧和動態規劃
2. 貪心演算法
這是最容易想到的解法,因為交易次數不受限,只需要在股價的每個上坡底部買入,坡頂賣出,則一定是利益最大化的
function maxProfit(prices){
let ans = 0;
for (let i = 0; i < prices.length - 1; i++) {
if (prices[i + 1] > prices[i]) { // 在上坡過程中,【每天都交易】和【底部買入,坡頂賣出】是完全等效的(忽略手續費)
ans += (prices[i + 1] - prices[i]);
}
}
return ans
}
3. 單調棧
單調棧顧名思義是單調遞增/減的一種棧結構,股價的上坡在資料結構上的表示,其實就是一個遞增的單調棧,我們只要依次找到所有的上坡,此時棧頂減去棧底,則是單次上坡的最大利潤
function maxProfit(prices){
//這裡只末尾+0就夠了
prices.push(0) //前後+0,是單調棧很常見的處理措施,確保起止元素一定能形成首個坡,終止末個的坡
let ans = 0
let stack = []
for (let i = 0; i < prices.length; i++) {
//stack[stack.length - 1] 單調棧棧頂,即本次上坡最大值
if(stack.length > 0 && prices[i] < stack[stack.length - 1]){
//棧頂
let top = stack[stack.length - 1]
//棧底
let bottom = stack[0]
ans += top - bottom
stack = []//清棧
}
stack.push(prices[i])
}
return ans
}
這裡主要講單調棧,所以保留了棧結構,實際本題比較簡單,只需記錄棧頂棧底即可
簡化後:
function maxProfit(prices){
prices.push(0)
let ans = 0
let top = prices[0]
let bottom = prices[0]
for (let i = 0; i < prices.length; i++) {
if(prices[i] >= top){
top = prices[i]
}else{
ans += top - bottom
top = bottom = prices[i]
}
}
return ans
}
本題是單調棧最基礎的應用,複雜點的比如接雨水,柱狀圖中最大的矩形,都是單調棧的應用場景,總之,單調棧是一個強大有趣的資料結構。
4. 動態規劃
不難得出每日只有持倉空倉兩種狀態:
對於今日持倉狀態,今天賬號的最大餘額為【昨日持倉】和【昨日空倉 - 今日股價】(買入所以扣錢)中的較大值
對於今日空倉狀態,今天賬號的最大餘額為【昨日空倉】和【昨日持倉 + 今日股價】(賣出所以加錢)中的較大值
最後一天平倉,即空倉狀態下賬號餘額就是最大收益
得到狀態轉移方程:
$$對於持倉狀態 f(i)_持 = max( f(i-1)_持, + f(i-1)_空 - price[i] )$$
$$對於空倉狀態 f(i)_空 = max( f(i-1)_空, + f(i-1)_持 + price[i] )$$
function maxProfit(prices){
//最初始的狀態
let dp = []
dp.push(
{
'positon': -prices[0],//持倉
'short_positon':0//空倉
}
)
for (let i = 1; i < prices.length; i++) {
let status = {
//本次選擇持倉,則賬戶最大金額為max(昨天持倉,昨天空倉-今日股價)
'positon': Math.max(dp[i-1].positon, dp[i-1].short_positon - prices[i]),
//本次選擇空倉,則賬戶最大金額為max(昨天空倉,昨天持倉+今日股價)
'short_positon': Math.max(dp[i-1].short_positon, dp[i-1].positon + prices[i])
}
}
return dp[prices.length-1].short_positon
}
由於只用得到昨日的資料,故而不用儲存每日的持倉狀態,只需要記錄昨天即可,
簡化後:
function maxProfit(prices){
//最初始的狀態
let positon = -prices[0] //持倉
let short_positon = 0 //空倉
for (let i = 1; i < prices.length; i++) {
let new_positon = Math.max(positon, short_positon - prices[i])
let new_short_positon = Math.max(short_positon, positon + prices[i])
positon = new_positon
short_positon = new_short_positon
}
return short_positon
}
動態規劃是一個強大有趣的演算法。