力扣309——最佳買賣股票時機含冷凍期

這道題主要涉及狀態轉移方程,想清楚全部狀態後,就能夠輕鬆解決。
<!-- more -->git

原題

給定一個整數數組,其中第 i 個元素表明了第 i 天的股票價格 。github

設計一個算法計算出最大利潤。在知足如下約束條件下,你能夠儘量地完成更多的交易(屢次買賣一支股票):算法

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

示例:segmentfault

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

原題url:https://leetcode-cn.com/probl...數組

解題

暴力解法

一開始我就是想着一共有幾種狀態,這幾種狀態分別能夠轉換爲哪些狀態:優化

  1. 當前是"持股狀態",能夠選擇繼續不交易,保持"持股狀態",或者選擇賣掉以後變成"冷凍狀態";
  2. 當前是"冷凍狀態",只能選擇繼續不交易,變成"不持股狀態";
  3. 當前是"不持股狀態",能夠選擇繼續不交易,保持"不持股狀態",或者選擇買股票以後變成"持股狀態";

我增長了最後終止條件:若是是最後一天,而且手上持有股票的話,必須賣出,這樣能夠保證最終利益最大。url

接下來看看代碼:spa

class Solution {
    int max = 0;

    public int maxProfit(int[] prices) {
        if (prices.length == 0) {
            return 0;
        }

        recursiveBuy(-1, false, 0, 0, prices);
        return max;
    }

    public void recursiveBuy(
        int prePrice,
        boolean cooldown,
        int profit,
        int index,
        int[] prices) {
        // 若是到了最後一天,而且手上持有股票的話,必須賣掉
        if (index == prices.length - 1) {
            if (prePrice >= 0 && !cooldown) {
                profit = profit + prices[index];
            }
            max = Math.max(max, profit);
            return;
        }

        // 當前持有股票
        if (prePrice >= 0) {
            // 此時能夠選擇不交易,或者賣掉
            // 不交易
            recursiveBuy(prePrice, cooldown, profit, index + 1, prices);
            // 賣掉
            recursiveBuy(-1, true, profit + prices[index], index + 1, prices);

            return;
        }

        // 當前不持有股票,能夠被動不交易、主動不交易、買

        // 若是處於冷凍期,只能被動不交易
        if (cooldown) {
            recursiveBuy(prePrice, false, profit, index + 1, prices);
            return;
        }

        // 不交易
        recursiveBuy(prePrice, cooldown, profit, index + 1, prices);
        // 買
        recursiveBuy(prices[index], cooldown, profit - prices[index], index + 1, prices);
    }
}

報了超出時間限制,好的,咱們想一想怎麼優化。設計

狀態轉移方程

上面暴力解法之因此會超時,由於重複計算了。我一開始的想法是想着記錄中間結果,但越想越複雜,忍不住看了別人的思路,真的是讓我豁然開朗。那就是狀態轉移方程code

以前我上面提到的是全部狀態能夠變成哪些狀態,但其實有些地方想的是不清楚的。咱們用箭頭鏈接兩個狀態,箭頭開始的那端表示前一天的狀態,箭頭終止的那端表示當天的狀態,那麼其內容爲:

由於買和賣只是兩個操做,咱們認爲只能在每一天的0點執行,當天的狀態就由0點以後的狀態來表示。

  • "冷凍期"狀態只能是昨天剛買了股票,也就是"不持股"狀態轉移過來。
  • "不持股"狀態能夠由本身,或者昨天是"持股"狀態,今天賣掉,轉移過來。
  • "持股"狀態能夠由本身,或者昨天是"冷凍期"狀態,今天買了,轉移過來。

你可能會問,若是這樣表示狀態轉移方程的話,那麼第一天能夠買入股票就無法解釋了。那簡單,爲了配合這種特殊狀況,咱們再記錄一個更早一天的不持股狀態,這樣就能夠知足了。

接下來看看代碼:

class Solution {
    public int maxProfit(int[] prices) {
        if (prices.length < 2) {
            return 0;
        }

        // 由於每次只涉及到前一天的三個狀態值,所以只要三個數字記錄便可
        /**
         * 其狀態轉移方程爲:
         * "冷凍期"只能由"不持股"轉換而來。
         * "持股"能夠由"持股"和"冷凍期"轉換而來。
         * "不持股"能夠由"不持股"和"持股"轉換而來。
         */
         // 定義初始狀況
         // 不持股
         int noStock = 0;
         // 持股
         int hasStock = -prices[0];
         // 冷凍期
         int cooldown = 0;
         // 上一次的不持股
         int beforeNoStock = 0;

         for (int i = 1; i < prices.length; i++) {
                     // "不持股"能夠由"不持股"和"持股"轉換而來。
             noStock = Math.max(beforeNoStock, hasStock + prices[i]);
                         // "持股"能夠由"持股"和"冷凍期"轉換而來。
             hasStock = Math.max(hasStock, cooldown - prices[i]);
                         // "冷凍期"只能由"不持股"轉換而來。
             cooldown = beforeNoStock;
                         // 更新一下"上一次的不持股"狀態
             beforeNoStock = noStock;
         }

         return Math.max(noStock, cooldown);
    }
}

提交OK。

狀態轉移方程繼續優化

其實從上面的分析,你隱約能夠察覺到,"冷凍期"就是一種特殊的"不持股"狀態。根據上面的結論,當你想買股票時,要求的是必須連續兩天"不持股",這點你想通了嗎?可能也正由於這一點,咱們在上面的代碼中才須要記錄"上一次的不持股"狀態。

既然這樣,咱們乾脆就簡化爲持股狀態和不持股狀態兩種,其狀態轉移方程能夠描述爲:

  • 持股狀態能夠由本身,或者連續兩天爲不持股狀態,今天買了股票,轉移而來。
  • 不持股狀態能夠由本身,或者前一天爲持股狀態,今天賣了股票,轉移而來。

由於咱們記錄的是每一天狀態所對應的收入,那麼所謂的連續兩天爲不持股狀態,就是至關於從兩天前收入不變。

接下來看看代碼:

class Solution {
    public int maxProfit(int[] prices) {
        if (prices.length < 2) {
            return 0;
        }

        // 此次只有兩個狀態,但須要記錄兩天前的不持股收入
        // 定義初始狀況
        // 不持股
        int noStock = 0;
        // 持股
        int hasStock = -prices[0];
        // 上一次的不持股
        int beforeNoStock = 0, temp;

        for (int i = 1; i < prices.length; i++) {
                    // 記錄一下"兩天前的不持股"收入
                    temp = noStock;
            // "不持股"能夠由"不持股"和"持股"轉換而來。
            noStock = Math.max(noStock, hasStock + prices[i]);
            // "持股"能夠由"持股"和"冷凍期"轉換而來。
            hasStock = Math.max(hasStock, beforeNoStock - prices[i]);
            // 更新一下"上一次的不持股"狀態
            beforeNoStock = temp;
        }
                
                // 最大值必定是最後一天不持股的狀況
        return noStock;
    }
}

提交OK。

總結

以上就是這道題目個人解答過程了,不知道你們是否理解了。狀態轉移應該仍是很經典的方法,主要在因而否能夠想出全部狀態及其轉化關係。

有興趣的話能夠訪問個人博客或者關注個人公衆號、頭條號,說不定會有意外的驚喜。

https://death00.github.io/

公衆號:健程之道

相關文章
相關標籤/搜索