代碼隨想錄算法訓(xùn)練營第41天 | 121. 買賣股票的最佳時(shí)機(jī)、122.買賣股票的最佳時(shí)機(jī)II、123.買賣股票的最佳時(shí)機(jī)III

第九章 動(dòng)態(tài)規(guī)劃part08

121. 買賣股票的最佳時(shí)機(jī)

文章講解

思路

  • 貪心的解法:
class Solution {
    public int maxProfit(int[] prices) {
        // 找到一個(gè)最小的購入點(diǎn)
        int low = Integer.MAX_VALUE;
        // res不斷更新,直到數(shù)組循環(huán)完畢
        int res = 0;
        for(int i = 0; i < prices.length; i++){
            low = Math.min(prices[i], low);
            res = Math.max(prices[i] - low, res);
        }
        return res;
    }
}
  • 動(dòng)態(tài)規(guī)劃
  1. 確定dp數(shù)組(dp table)以及下標(biāo)的含義
    dp[i][0] 表示第i天持有股票所得最多現(xiàn)金 ,dp[i][1] 表示第i天不持有股票所得最多現(xiàn)金

  2. 確定遞推公式
    如果第i天持有股票即dp[i][0], 那么可以由兩個(gè)狀態(tài)推出來

    • 第i-1天就持有股票,那么就保持現(xiàn)狀,所得現(xiàn)金就是昨天持有股票的所得現(xiàn)金 即:dp[i - 1][0]
    • 第i天買入股票,所得現(xiàn)金就是買入今天的股票后所得現(xiàn)金即:-prices[i]
    • 那么dp[i][0]應(yīng)該選所得現(xiàn)金最大的,所以dp[i][0] = max(dp[i - 1][0], -prices[i]);

    如果第i天不持有股票即dp[i][1], 也可以由兩個(gè)狀態(tài)推出來

    • 第i-1天就不持有股票,那么就保持現(xiàn)狀,所得現(xiàn)金就是昨天不持有股票的所得現(xiàn)金 即:dp[i - 1][1]
    • 第i天賣出股票,所得現(xiàn)金就是按照今天股票價(jià)格賣出后所得現(xiàn)金即:prices[i] + dp[i - 1][0]
    • 同樣dp[i][1]取最大的,dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);
  3. dp數(shù)組如何初始化
    由遞推公式 dp[i][0] = max(dp[i - 1][0], -prices[i]); 和 dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);可以看出其基礎(chǔ)都是要從dp[0][0]和dp[0][1]推導(dǎo)出來。
    那么dp[0][0]表示第0天持有股票,此時(shí)的持有股票就一定是買入股票了,因?yàn)椴豢赡苡星耙惶焱瞥鰜恚?code>dp[0][0] -= prices[0];
    dp[0][1]表示第0天不持有股票,不持有股票那么現(xiàn)金就是0,所以dp[0][1] = 0;

  4. 確定遍歷順序
    從遞推公式可以看出dp[i]都是由dp[i - 1]推導(dǎo)出來的,那么一定是從前向后遍歷。

  5. 舉例推導(dǎo)dp數(shù)組
    以示例1,輸入:[7,1,5,3,6,4]為例,dp數(shù)組狀態(tài)如下:


    image.png
class Solution {
    public int maxProfit(int[] prices) {
        //版本1
        int len = prices.length;
        if(prices == null || len == 0) return 0;

        // dp[i][0]代表第i天持有股票的最大收益
        // dp[i][1]代表第i天不持有股票的最大收益
        int[][] dp = new int[len][2];
        dp[0][0] = -prices[0];
        dp[0][1] = 0;
        for(int i = 1; i < len; i++){
            dp[i][0] = Math.max(dp[i-1][0], -prices[i]);
            dp[i][1] = Math.max(dp[i-1][1], prices[i] + dp[i-1][0]);
        }
        return dp[len-1][1];
    }
}

從遞推公式可以看出,dp[i]只是依賴于dp[i - 1]的狀態(tài)。

dp[i][0] = max(dp[i - 1][0], -prices[i]);
dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);

那么我們只需要記錄 當(dāng)前天的dp狀態(tài)和前一天的dp狀態(tài)就可以了,可以使用滾動(dòng)數(shù)組來節(jié)省空間
i % 2 的作用是用來在 dp 數(shù)組的兩行之間切換,使得我們能夠復(fù)用數(shù)組空間。例如:

  • 當(dāng) i 為偶數(shù)時(shí),i % 2 為 0,表示使用 dp 數(shù)組的第 0 行來存儲(chǔ)第 i 天的狀態(tài)。
  • 當(dāng) i 為奇數(shù)時(shí),i % 2 為 1,表示使用 dp 數(shù)組的第 1 行來存儲(chǔ)第 i 天的狀態(tài)。

這樣,我們就可以使用 dp[0]dp[1] 兩行來分別存儲(chǔ)當(dāng)前和前一天的狀態(tài),達(dá)到節(jié)省空間的目的。

class Solution {
    public int maxProfit(int[] prices) {
        //版本1
        int len = prices.length;
        if(prices == null || len == 0) return 0;

        // dp[i][0]代表第i天持有股票的最大收益
        // dp[i][1]代表第i天不持有股票的最大收益
        int[][] dp = new int[2][2];
        dp[0][0] = -prices[0];
        dp[0][1] = 0;
        for(int i = 1; i < len; i++){
            dp[i % 2][0] = Math.max(dp[(i-1) % 2][0], -prices[i]);
            dp[i % 2][1] = Math.max(dp[(i-1) % 2][1], prices[i] + dp[(i-1) % 2][0]);
        }
        return dp[(len-1) % 2][1];
    }
}

122.買賣股票的最佳時(shí)機(jī)II

文章講解

思路
dp數(shù)組的含義:

  • dp[i][0] 表示第i天持有股票所得現(xiàn)金。
  • dp[i][1] 表示第i天不持有股票所得最多現(xiàn)金
    注意這里和121. 買賣股票的最佳時(shí)機(jī) (opens new window)唯一不同的地方,就是推導(dǎo)dp[i][0]的時(shí)候,第i天買入股票的情況
  • 本題,因?yàn)橐恢还善笨梢再I賣多次,所以當(dāng)?shù)趇天買入股票的時(shí)候,所持有的現(xiàn)金可能有之前買賣過的利潤。
    那么第i天持有股票即dp[i][0],如果是第i天買入股票,所得現(xiàn)金就是昨天不持有股票的所得現(xiàn)金 減去 今天的股票價(jià)格 即:dp[i - 1][1] - prices[i]
    再來看看如果第i天不持有股票即dp[i][1]的情況, 依然可以由兩個(gè)狀態(tài)推出來
    • 第i-1天就不持有股票,那么就保持現(xiàn)狀,所得現(xiàn)金就是昨天不持有股票的所得現(xiàn)金 即:dp[i - 1][1]
    • 第i天賣出股票,所得現(xiàn)金就是按照今天股票價(jià)格賣出后所得現(xiàn)金即:prices[i] + dp[i - 1][0]
class Solution {
    public int maxProfit(int[] prices) {
        int[] dp = new int[2];
        // 0代表持有,1代表賣出
        dp[0] = -prices[0];
        dp[1] = 0;
        for(int i = 1; i <= prices.length; i++){
            //第i天持有; 或前一天持有(前一天持有的話,之前就是賣出的狀態(tài),就要用dp[1]減去前一天的價(jià)格)
            dp[0] = Math.max(dp[0], dp[1] - prices[i - 1]);
            //第i天賣出;或前一天賣出(前一天賣出的話,先前就是持有的狀態(tài),再加上前一天的收益)
            dp[1] = Math.max(dp[1], dp[0] + prices[i - 1]);
        }
        return dp[1]; //二維數(shù)組應(yīng)該返回length-1,但是這里改了for循環(huán)里 i <= prices.length
    }
}

123.買賣股票的最佳時(shí)機(jī)III

這道題一下子就難度上來了,關(guān)鍵在于至多買賣兩次,這意味著可以買賣一次,可以買賣兩次,也可以不買賣。
文章講解

思路

  • 關(guān)鍵在于至多買賣兩次,這意味著可以買賣一次,可以買賣兩次,也可以不買賣。
  1. 確定dp數(shù)組以及下標(biāo)的含義
    一天一共就有五個(gè)狀態(tài),

    • 沒有操作 (其實(shí)我們也可以不設(shè)置這個(gè)狀態(tài))
    • 第一次持有股票
    • 第一次不持有股票
    • 第二次持有股票
    • 第二次不持有股票
      dp[i][j]中 i表示第i天,j為 [0 - 4] 五個(gè)狀態(tài),dp[i][j]表示第i天狀態(tài)j所剩最大現(xiàn)金。
      需要注意:dp[i][1],表示的是第i天,買入股票的狀態(tài),并不是說一定要第i天買入股票
  2. 確定遞推公式

  • 達(dá)到dp[i][1]狀態(tài),有兩個(gè)具體操作:

    • 操作一:第i天買入股票了,那么dp[i][1] = dp[i-1][0] - prices[i]
    • 操作二:第i天沒有操作,而是沿用前一天買入的狀態(tài),即:dp[i][1] = dp[i - 1][1]
    • dp[i][1]一定是選最大的,所以dp[i][1] = max(dp[i-1][0] - prices[i], dp[i - 1][1]);
  • 同理dp[i][2]也有兩個(gè)操作:

    • 操作一:第i天賣出股票了,那么dp[i][2] = dp[i - 1][1] + prices[i]
    • 操作二:第i天沒有操作,沿用前一天賣出股票的狀態(tài),即:dp[i][2] = dp[i - 1][2]
    • 所以dp[i][2] = max(dp[i - 1][1] + prices[i], dp[i - 1][2])
  • 同理可推出剩下狀態(tài)部分:
    dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - prices[i]);
    dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]);

  1. dp數(shù)組如何初始化
    第0天沒有操作,這個(gè)最容易想到,就是0,即:dp[0][0] = 0;
    第0天做第一次買入的操作,dp[0][1] = -prices[0];
    第0天做第一次賣出的操作,這個(gè)初始值應(yīng)該是多少呢?
    此時(shí)還沒有買入,怎么就賣出呢? 其實(shí)大家可以理解當(dāng)天買入,當(dāng)天賣出,所以dp[0][2] = 0;
    第0天第二次買入操作,初始值應(yīng)該是多少呢?應(yīng)該不少同學(xué)疑惑,第一次還沒買入呢,怎么初始化第二次買入呢?
    第二次買入依賴于第一次賣出的狀態(tài),其實(shí)相當(dāng)于第0天第一次買入了,第一次賣出了,然后再買入一次(第二次買入),那么現(xiàn)在手頭上沒有現(xiàn)金,只要買入,現(xiàn)金就做相應(yīng)的減少。
    所以第二次買入操作,初始化為:dp[0][3] = -prices[0];
    同理第二次賣出初始化dp[0][4] = 0;

  2. 確定遍歷順序
    從遞歸公式其實(shí)已經(jīng)可以看出,一定是從前向后遍歷,因?yàn)閐p[i],依靠dp[i - 1]的數(shù)值。

  3. 舉例推導(dǎo)dp數(shù)組
    以輸入[1,2,3,4,5]為例


    image.png
class Solution {
    public int maxProfit(int[] prices) {
        int len = prices.length;

        /*
         * 定義 5 種狀態(tài):
         * 0: 沒有操作, 1: 第一次買入, 2: 第一次賣出, 3: 第二次買入, 4: 第二次賣出
         */
        int[][] dp = new int[len][5];
        dp[0][1] = -prices[0];
        dp[0][3] = -prices[0];
        for(int i = 1; i < len; i++){
            dp[i][1] = Math.max(dp[i - 1][1], 0 - prices[i]);
            dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][1] + prices[i]);
            dp[i][3] = Math.max(dp[i - 1][3], dp[i - 1][2] - prices[i]);
            dp[i][4] = Math.max(dp[i - 1][4], dp[i - 1][3] + prices[i]);
        }
        return dp[len - 1][4];
         
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,885評(píng)論 6 541
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,312評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,993評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,667評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,410評(píng)論 6 411
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,778評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,775評(píng)論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,955評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,521評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,266評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,468評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,998評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,696評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,095評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,385評(píng)論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,193評(píng)論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,431評(píng)論 2 378

推薦閱讀更多精彩內(nèi)容