「leetcode」買賣股票的最佳時機I、II、III、IV、含手續費、含冷凍期

前言

出場率很高的系列面試題,半年內,頭條18次,阿里4次,小米3次(我買了會員所以可以看到出場率)。最近要面試的必看。

121.買賣股票的最佳時機(簡單)

原題

給定一個數組,它的第 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。

思路

1.png

1.png

注意:本題題限制交易次數爲1次。

  • i

    • 如果第i天持有股票。有兩種可能會在第i天持有股票。

      1. 今天購買股票
      2. 今天之前就已經購買股票
    • 如果第i天不持有股票。有兩種可能會在第i天不持有股票。

      1. 今天本身就不持有股票
      2. 今天賣出本身持有的股票
  • 第一天,股價爲7

    • 由購買股票屬於支出,所有如果在第一天購買股票,最大利潤爲0 - 7 = -7
    • 第一天不持有股票,利潤爲0
  • 第二天,股價爲1

    • 我們需要將利潤最大化,所以購買的股價越低越好。第二天股價爲1是優於第一天的7的,所以我們應該選擇在第二天購買股票,此時最大利潤爲0 - 1 = -1。(由於本題本身限制了交易次數,所以不存在被除數不等於0的情況)
    • 第二天不持有股票,有兩種可能(第二天本身就不持有股票,狀態沿用前一天的狀態。或者,以️今天的股價賣出當前持有的股票。)。第一種可能,最大收益爲0, 第二種可能,最大收益爲1 - 7 = -6,小於0。所以我們在第二天的最大收益應該是0
  • 第三天,股價爲5

    • 第三天股價爲5比前一天要高,所以我們依然選擇在前一天購買股票,持有股票時第三天最大利潤依然爲-1
    • 第三天不持有股票,有兩種可能。第一種可能是沿用前一日的狀態,收益依然爲0。 第二種可能,以今天的股價賣出當前持有股票,收益爲5 - 1 = 4(當前的股價 - 前一日持有股票時的最優值或者說成本最低值),結果是4,優於第一種可能。所以在第三天不持有股票的最大收益爲4

向後依次類推,找出,不持有股票的最優結果數組中的,最大值即可。💡(因爲不持有股票時,纔沒有負擔啊~~)。

代碼


var maxProfit = function(prices) {
    if (prices.length === 0 || prices.length === 1) {
        return 0
    }
    // dp1數組存儲第`i`天,持有股票的最大利潤
    const dp1 = []
    dp1[0] = -prices[0]
    // dp2數組存儲第`i`天,不持有股票的最大利潤
    const dp2 = []
    dp2[0] = 0
    
    for (let i = 1; i < prices.length; i++) {
        dp1[i] = Math.max(dp1[i - 1], -prices[i])
        dp2[i] = Math.max(dp2[i - 1], prices[i] + dp1[i - 1])
    }
    
    return dp2[dp2.length - 1]
};

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 天接連購買股票,之後再將它們賣出。
     因爲這樣屬於同時參與了多筆交易,你必須在再次購買前出售掉之前的股票。

思路

2.png

2.png

注意:本題不限制交易次數(309題,714題都可以看作是本題的引申)

與第一題不同,本題是可以進行無限次的交易。所以我們的收益是可以累加的。對狀態轉移方程式的影響,最直接的體現,就是對於i天持有股票的最優解我們需要考慮前一天收益,而不是從0開始。所以,持有股票的狀態轉移方程式由,Max(前一天的狀態, 0 - 今天的股價)變爲Max(前一天的狀態,前一天的最優收益 - 今天的股價)

我們按照上題的推導邏輯,依次類推即可。最後,返回最後一天不持有股票時的最佳收益即可。

代碼


/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function(prices) {
    if (prices.length === 0 || prices.length === 1) {
        return 0
    }
    
    const dp1 = []
    dp1[0] = -prices[0]
    
    const dp2 = []
    dp2[0] = 0
    
    for (let i = 1; i < prices.length; i++) {
        dp1[i] = Math.max(dp1[i - 1], dp2[i - 1] - prices[i])
        dp2[i] = Math.max(dp2[i - 1], prices[i] + dp1[i - 1])
    }
    
    return dp2[prices.length - 1]
};

714.買賣股票的最佳時機含手續費(中等)

原題

給定一個整數數組 prices,其中第 i 個元素代表了第 i 天的股票價格 ;非負整數 fee 代表了交易股票的手續費用。你可以無限次地完成交易,但是你每次交易都需要付手續費。如果你已經購買了一個股票,在賣出它之前你就不能再繼續購買股票了。返回獲得利潤的最大值。

示例 1:

輸入: prices = [1, 3, 2, 8, 4, 9], fee = 2
輸出: 8
解釋: 能夠達到的最大利潤:  
在此處買入 prices[0] = 1
在此處賣出 prices[3] = 8
在此處買入 prices[4] = 4
在此處賣出 prices[5] = 9
總利潤: ((8 - 1) - 2) + ((9 - 4) - 2) = 8.

思路

3.png

本題是122題的變種,我們只需要在賣出股票時,考慮手續費fee即可。

代碼


/**
 * @param {number[]} prices
 * @param {number} fee
 * @return {number}
 */
var maxProfit = function(prices, fee) {
    if (prices.length === 0 || prices.length === 1) {
        return 0
    }
    
    const dp1 = []
    dp1[0] = -prices[0]

    const dp2 = []
    dp2[0] = 0
    
    for (let i = 1; i < prices.length; i++) {
        dp1[i] = Math.max(dp1[i - 1], dp2[i - 1] - prices[i])
        dp2[i] = Math.max(dp2[i - 1], prices[i] + dp1[i - 1] - fee)
    }
    
    return dp2[dp2.length - 1]
};

309.最佳買賣股票時機含冷凍期(中等)

原題

給定一個整數數組,其中第 i 個元素代表了第 i 天的股票價格 。​設計一個算法計算出最大利潤。在滿足以下約束條件下,你可以儘可能地完成更多的交易(多次買賣一支股票):

  • 你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。
  • 賣出股票後,你無法在第二天買入股票 (即冷凍期爲 1 天)。

示例:

輸入: [1,2,3,0,2]
輸出: 3 
解釋: 對應的交易狀態爲: [買入, 賣出, 冷凍期, 買入, 賣出]

思路

4.png

本題依然是122題的變種,但是又有所區別。

交易股票分爲買入賣出兩步。買入的操作是受到了冷凍期的限制,但是賣出是不受冷凍期的限制的。根據冷凍期的規則,我們需要將持有股票的狀態轉移方程調整爲上圖所示。但是很多人看到上面的公式,都會提出下面這個疑問。

🤔️Q:第i天持有股票的最優狀態爲什麼要從第i - 2天不持有的狀態獲得,第i-1天就一定是冷凍期嗎?

😆A: 第i天持有股票的方式有兩種可能。第一種可能,本身就持有股票,延續i-1天的狀態。第二種可能,以第i天的股價購買股票,那麼我們就需要考慮,第i-1天不持有股票狀態。

如果第i-1天是冷凍期。那麼第i-1天的狀態必然和第i-2天相等。

如果第i-1天不是是冷凍期。有兩種可能。第一種可能,i-1天本身就不持有股票,那麼我們就需要得到第i-2天的狀態。第二種可能,第i-1天賣出股票,由於冷凍期的限制,第i天就不能購買股票,所以這種可能不成立(無論是不是冷凍期,我們都不能在第i-1天賣出股票)。

通過上面的分析可知。無論第i-1天是不是冷凍期,我們都要從第i-2天不持有股票的最優解進行計算。

代碼


/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function(prices) {
    if (prices.length === 0 || prices.length === 1) {
        return 0
    }
    
    const dp1 = []
    dp1[0] = -prices[0]

    const dp2 = []
    dp2[0] = 0
    
    for (let i = 1; i < prices.length; i++) {
        let temp = dp2[i - 2] === undefined ? 0 : dp2[i - 2]
        dp1[i] = Math.max(dp1[i - 1], temp - prices[i])
        dp2[i] = Math.max(dp2[i - 1], prices[i] + dp1[i - 1])
    }
    
    return dp2[dp2.length - 1]
};

123.買賣股票的最佳時機 III(困難)

原題

給定一個數組,它的第 i 個元素是一支給定的股票在第 i 天的價格。

設計一個算法來計算你所能獲取的最大利潤。你最多可以完成 兩筆 交易。

注意: 你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。

示例 1:

輸入: [3,3,5,0,0,3,1,4]
輸出: 6
解釋: 在第 4 天(股票價格 = 0)的時候買入,在第 6 天(股票價格 = 3)的時候賣出,這筆交易所能獲得利潤 = 3-0 = 3 。
     隨後,在第 7 天(股票價格 = 1)的時候買入,在第 8 天 (股票價格 = 4)的時候賣出,這筆交易所能獲得利潤 = 4-1 = 3 。

示例 2:

輸入: [1,2,3,4,5]
輸出: 4
解釋: 在第 1 天(股票價格 = 1)的時候買入,在第 5 天 (股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。   
     注意你不能在第 1 天和第 2 天接連購買股票,之後再將它們賣出。   
     因爲這樣屬於同時參與了多筆交易,你必須在再次購買前出售掉之前的股票。

思路

5.png

本題的思考需要增加一個緯度,之前我們只需要考慮持有不持有兩種情況,我們在兩種情況上,增加到四種情況。持有股票,還有兩次交易機會持有股票,還有一次交易機會不持有股票,還有兩次交易機會不持有股票,還有一次交易機會

代碼


/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function(prices) {
    if (prices.length === 0 || prices.length === 1) {
        return 0
    }
    
    // 持有股票
    const dp1 = [
        [-prices[0]], // 還剩下兩次交易機會
        [-prices[0]] // 還剩下一次交易機會
    ]
    // 不持有股票
    const dp2 = [
        [0], // 還剩下兩次交易機會
        [0] // 還剩下一次交易機會
    ]
    
    for (let i = 1; i < prices.length; i++) {
        // 持有股票,還有兩次交易機會
        dp1[0][i] = Math.max(dp1[0][i - 1], -prices[i])
        // 持有股票,還有一次交易機會
        dp1[1][i] = Math.max(dp1[1][i - 1], dp2[0][i - 1] - prices[i])
        // 不持有股票,還有兩次交易機會
        dp2[0][i] = Math.max(dp2[0][i - 1], prices[i] + dp1[0][i - 1]) 
        // 不持有股票,還有一次交易機會
        dp2[1][i] = Math.max(dp2[1][i - 1], prices[i] + dp1[1][i - 1])
    }
    
    return dp2[1][prices.length - 1]
};

188.買賣股票的最佳時機 IV(困難)

原題

給定一個數組,它的第 i 個元素是一支給定的股票在第 i 天的價格。

設計一個算法來計算你所能獲取的最大利潤。你最多可以完成 k 筆交易。

注意: 你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。

示例 1:

輸入: [2,4,1], k = 2
輸出: 2
解釋: 在第 1 天 (股票價格 = 2) 的時候買入,在第 2 天 (股票價格 = 4) 的時候賣出,這筆交易所能獲得利潤 = 4-2 = 2 。

示例 2:

輸入: [3,2,6,5,0,3], k = 2
輸出: 7
解釋: 在第 2 天 (股票價格 = 2) 的時候買入,在第 3 天 (股票價格 = 6) 的時候賣出, 這筆交易所能獲得利潤 = 6-2 = 4 。
     隨後,在第 5 天 (股票價格 = 0) 的時候買入,在第 6 天 (股票價格 = 3) 的時候賣出, 這筆交易所能獲得利潤 = 3-0 = 3 。
   

思路

本題是123題的衍生,在leetcode用例中,會有因爲k過大,導致dp數組過大,導致內存溢出的問題。

長度爲n的數組,最多進行n/2次的交易,所以所以k大於n/2,就相當於不限制交易次數, 我們就可以把本題當作第122題處理。

代碼


/**
 * @param {number} k
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function(k, prices) {
    if (prices.length === 0 || prices.length === 1) {
        return 0
    }
    
    if (k === 0) {
        return 0
    }
    
    if (k > prices.length / 2) {
        k = -1
    }
    
    const dp1 = []
    const dp2 = []
    
    if (k == -1) {
        dp1[0] = -prices[0]
        dp2[0] = 0
    } else {
        for (let i = 0; i < k; i++) {
            dp1.push([-prices[0]])
            dp2.push([0])
        }
    }
    
    for (let i = 1; i < prices.length; i++) {
        if (k === -1) {
            dp1[i] = Math.max(dp1[i - 1], dp2[i - 1] - prices[i])
            dp2[i] = Math.max(dp2[i - 1], prices[i] + dp1[i - 1])
        } else {
            for (let j = 0; j < k; j++) {
                if (j === 0) {
                    dp1[j][i] = Math.max(dp1[j][i - 1], -prices[i]) 
                } else {
                    dp1[j][i] = Math.max(dp1[j][i - 1], dp2[j - 1][i - 1] - prices[i])
                }
                dp2[j][i] = Math.max(dp2[j][i - 1], prices[i] + dp1[j][i - 1]) 
                dp2[j][i] = Math.max(dp2[j][i - 1], prices[i] + dp1[j][i - 1])
            }
        }
    }
    
    if (k === -1) {
        return dp2[prices.length - 1]
    } else {
        return dp2[k - 1][prices.length - 1]
    }
};

QA

🤔️Q:今天的最優解,是基於前一天的最優解得到的,這樣可以保證全局最優嗎?

😆A:我們可以把每一天當作最後一天。在最後一天,我們️收益值基於兩種可能。第一種可能,最後一天時我們沒有股票,
由於是最後一天所以,我們也不能買入股票,只能維持前一天的收益值。第二種可能,最後我們手上恰好還有股票,我們只能選擇將它賣出,我們的收益值取決於當天的股價以及買入的價格(持有股票的最優值),當天的股價是確定以及肯定的,如果買入的價格不是最優值,我們必然無法得到收益的最大值。

參考

[Most consistent ways of dealing with the series of stock problems
](https://leetcode.com/problems...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章