代碼隨想錄算法訓(xùn)練營(yíng)第42天 |188.買賣股票的最佳時(shí)機(jī)IV筋讨、309.最佳買賣股票時(shí)機(jī)含冷凍期、714.買賣股票的最佳時(shí)機(jī)含手續(xù)費(fèi)

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

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

本題是123.買賣股票的最佳時(shí)機(jī)III 的進(jìn)階版
文章講解

思路

  1. 確定dp數(shù)組以及下標(biāo)的含義
    動(dòng)態(tài)規(guī)劃:123.買賣股票的最佳時(shí)機(jī)III (opens new window)
    睁搭,使用二維數(shù)組 dp[i][j] :第i天的狀態(tài)為j赶诊,所剩下的最大現(xiàn)金是dp[i][j],實(shí)際上除了0以外园骆,j的狀態(tài)表示偶數(shù)就是賣出舔痪,奇數(shù)就是買入。本題也可以用二維數(shù)組表示锌唾,題目要求是至多有K筆交易锄码,那么j的范圍就定義為 2 * k + 1 就可以了。只是奇數(shù)代表買入鸠珠,偶數(shù)代表賣出巍耗。
  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] = 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)霎迫,代碼如下:
for (int j = 0; j < 2 * k - 1; j += 2) {
    dp[i][j + 1] = max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]);
    dp[i][j + 2] = max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]);
}

最大的區(qū)別就是這里要類比j為奇數(shù)是買,偶數(shù)是賣的狀態(tài)帘靡。

  1. dp數(shù)組如何初始化
  • 第0天沒有操作知给,這個(gè)最容易想到,就是0描姚,即:dp[0][0] = 0;
  • 第0天做第一次買入的操作涩赢,dp[0][1] = -prices[0];
  • 第0天做第一次賣出的操作,可以理解為當(dāng)天買入轩勘,當(dāng)天賣出筒扒,所以dp[0][2] = 0;
  • 第0天第二次買入操作:第二次買入依賴于第一次賣出的狀態(tài),其實(shí)相當(dāng)于第0天第一次買入了绊寻,第一次賣出了花墩,然后在買入一次(第二次買入),那么現(xiàn)在手頭上沒有現(xiàn)金澄步,只要買入冰蘑,現(xiàn)金就做相應(yīng)的減少。
    所以第二次買入操作驮俗,初始化為:dp[0][3] = -prices[0];
  • 第二次賣出初始化dp[0][4] = 0;
    所以同理可以推出dp[0][j]當(dāng)j為奇數(shù)的時(shí)候都初始化為 -prices[0]
    以此分別類比奇偶天數(shù)的狀態(tài)
for (int j = 1; j < 2 * k; j += 2) {
    dp[0][j] = -prices[0];
}
  1. 確定遍歷順序
    從遞歸公式其實(shí)已經(jīng)可以看出懂缕,一定是從前向后遍歷,因?yàn)閐p[i]王凑,依靠dp[i - 1]的數(shù)值搪柑。

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


    image.png
class Solution {
    public int maxProfit(int k, int[] prices) {
        if(prices.length == 0) return 0;
        int len = prices.length;
        // [天數(shù)][股票狀態(tài)]
        // 股票狀態(tài): 奇數(shù)表示第 k 次交易持有, 偶數(shù)表示第 k 次交易不持有, 0 表示沒有操作
        int[][] dp = new int[len][2 * k + 1];
        for(int i = 1; i < k * 2; i += 2){
            dp[0][i] = - prices[0];
        }
        for(int i = 1; i < len; i++){
            for(int j = 0; j <= 2 * k - 1; j += 2){
                dp[i][j + 1] = Math.max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]);
                dp[i][j + 2] = Math.max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]);
            }
        }
        return dp[len - 1][k * 2];
    }
}
  • 為什么在 for (int j = 0; j < k*2 - 1; j += 2) 中工碾, j < k*2 - 1
    for (int j = 0; j < k*2 - 1; j += 2) 循環(huán)中,j 的上限是 k*2 - 1
    假設(shè) k 是允許的最大交易次數(shù):
  • k * 2 是我們需要考慮的所有狀態(tài)的總數(shù)百姓,因?yàn)槊看谓灰装I入和賣出兩個(gè)狀態(tài)渊额。
  • k * 2 - 1j 的最大有效值,因?yàn)?j 達(dá)到 k * 2 時(shí)已經(jīng)是一個(gè)完整交易的結(jié)束狀態(tài)垒拢。

for 循環(huán)中:

  • j 的起點(diǎn)是 0旬迹,這表示沒有進(jìn)行任何操作的狀態(tài)。
  • j 每次增加 2求类,確保我們只在需要更新交易狀態(tài)的位置進(jìn)行操作(即處理買入或賣出的狀態(tài))奔垦。
    具體解釋代碼中的循環(huán):
for (int j = 0; j < k*2 - 1; j += 2) {
    // 更新買入狀態(tài): 第 j / 2 + 1 次交易買入的最大利潤(rùn)
    dp[i][j + 1] = Math.max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]);
    // 更新賣出狀態(tài): 第 j / 2 + 1 次交易賣出的最大利潤(rùn)
    dp[i][j + 2] = Math.max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]);
}
  • j + 1 更新的是買入狀態(tài)。
  • j + 2 更新的是賣出狀態(tài)尸疆。

由于 j 的最大值為 k * 2 - 1椿猎,所以 j + 1 的最大值為 k * 2 - 1(代表第 k 次交易的買入狀態(tài)),而 j + 2 的最大值為 k * 2(代表第 k 次交易的賣出狀態(tài))寿弱。

這就是為什么 for 循環(huán)中的 j 條件是 j < k * 2 - 1 的原因犯眠。


309.最佳買賣股票時(shí)機(jī)含冷凍期

本題加了一個(gè)冷凍期,狀態(tài)就多了症革,有點(diǎn)難度筐咧,大家要把各個(gè)狀態(tài)分清,思路才能清晰
文章講解

思路
具體可以區(qū)分出如下四個(gè)狀態(tài):

  • 狀態(tài)一:持有股票狀態(tài)(今天買入股票噪矛,或者是之前就買入了股票然后沒有操作量蕊,一直持有)
  • 不持有股票狀態(tài),這里就有兩種賣出股票狀態(tài)
    • 狀態(tài)二:保持賣出股票的狀態(tài)(兩天前就賣出了股票摩疑,度過一天冷凍期危融。或者是前一天就是賣出股票狀態(tài)雷袋,一直沒操作)
    • 狀態(tài)三:今天賣出股票
  • 狀態(tài)四:今天為冷凍期狀態(tài)吉殃,但冷凍期狀態(tài)不可持續(xù),只有一天楷怒!
  1. 確定dp數(shù)組以及下標(biāo)的含義
    dp[i][j]蛋勺,第i天狀態(tài)為j,所剩的最多現(xiàn)金為dp[i][j]鸠删。

  2. 確定遞推公式

達(dá)到買入股票狀態(tài)(狀態(tài)一)即:dp[i][0]抱完,有兩個(gè)具體操作:

  • 操作一:前一天就是持有股票狀態(tài)(狀態(tài)一),dp[i][0] = dp[i - 1][0]
  • 操作二:今天買入了刃泡,有兩種情況
    • 前一天是冷凍期(狀態(tài)四)巧娱,dp[i - 1][3] - prices[i]
    • 前一天是保持賣出股票的狀態(tài)(狀態(tài)二)碉怔,dp[i - 1][1] - prices[i]

那么dp[i][0] = max(dp[i - 1][0], dp[i - 1][3] - prices[i], dp[i - 1][1] - prices[i]);

達(dá)到保持賣出股票狀態(tài)(狀態(tài)二)即:dp[i][1],有兩個(gè)具體操作:

  • 操作一:前一天就是狀態(tài)二
  • 操作二:前一天是冷凍期(狀態(tài)四)
    dp[i][1] = max(dp[i - 1][1], dp[i - 1][3]);

達(dá)到今天就賣出股票狀態(tài)(狀態(tài)三)禁添,即:dp[i][2] 撮胧,只有一個(gè)操作:

  • 昨天一定是持有股票狀態(tài)(狀態(tài)一),今天賣出
    即:dp[i][2] = dp[i - 1][0] + prices[i];

達(dá)到冷凍期狀態(tài)(狀態(tài)四)老翘,即:dp[i][3]芹啥,只有一個(gè)操作:

  • 昨天賣出了股票(狀態(tài)三)
    dp[i][3] = dp[i - 1][2];

綜上分析,遞推代碼如下:

dp[i][0] = Math.max(dp[i-1][0], dp[i-1][3] - prices[i], dp[i-1][1] - prices[i]);
dp[i][1] = Math.max(dp[i-1][1], dp[i-1][3]);
dp[i][2] = Math.max(dp[i-1][0] + prices[i]);
dp[i][3] = dp[i-1][2];
  1. dp數(shù)組如何初始化

這里主要討論一下第0天如何初始化铺峭。

如果是持有股票狀態(tài)(狀態(tài)一)那么:dp[0][0] = -prices[0]墓怀,一定是當(dāng)天買入股票。

保持賣出股票狀態(tài)(狀態(tài)二)卫键,這里其實(shí)從 「狀態(tài)二」的定義來說 傀履,很難明確應(yīng)該初始多少,這種情況我們就看遞推公式需要我們給他初始成什么數(shù)值永罚。

如果i為1啤呼,第1天買入股票,那么遞歸公式中需要計(jì)算 dp[i - 1][1] - prices[i] 呢袱,即 dp[0][1] - prices[1]官扣,那么大家感受一下 dp[0][1] (即第0天的狀態(tài)二)應(yīng)該初始成多少,只能初始為0羞福。想一想如果初始為其他數(shù)值惕蹄,是我們第1天買入股票后 手里還剩的現(xiàn)金數(shù)量是不是就不對(duì)了。

今天賣出了股票(狀態(tài)三)治专,同上分析卖陵,dp[0][2]初始化為0,dp[0][3]也初始為0张峰。

  1. 確定遍歷順序
    從遞歸公式上可以看出泪蔫,dp[i] 依賴于 dp[i-1],所以是從前向后遍歷喘批。

  2. 舉例推導(dǎo)dp數(shù)組
    以 [1,2,3,0,2] 為例撩荣,dp數(shù)組如下:


    image.png
class Solution {
    public int maxProfit(int[] prices) {
        if(prices == null) return 0;
        int len = prices.length;
        int[][] dp = new int[len][4];
        /**
            1. [i][0] holding the stock
            2. [i][1] after cooldown but stil not buing the stock
            3. [i][2] selling the stock
            4. [i][3] cooldown
        */
        dp[0][0] = -prices[0];
        for(int i = 1; i < len; i++){
            dp[i][0] = Math.max(Math.max(dp[i-1][0], dp[i-1][3] - prices[i]), dp[i-1][1] - prices[i]);
            dp[i][1] = Math.max(dp[i-1][1], dp[i-1][3]);
            dp[i][2] = dp[i-1][0] + prices[i];
            dp[i][3] = dp[i-1][2];
        }
        return Math.max(dp[len-1][3], Math.max(dp[len-1][1], dp[len-1][2]));
    }
}

714.買賣股票的最佳時(shí)機(jī)含手續(xù)費(fèi)

相對(duì)122.買賣股票的最佳時(shí)機(jī)II ,本題只需要在計(jì)算賣出操作的時(shí)候減去手續(xù)費(fèi)就可以了饶深,代碼幾乎是一樣的餐曹,可以嘗試自己做一做。
文章講解

思路
相對(duì)于 122.買賣股票的最佳時(shí)機(jī)II敌厘,本題只需要在計(jì)算賣出操作的時(shí)候減去手續(xù)費(fèi)就可以了台猴,代碼幾乎是一樣的。

唯一差別在于遞推公式

  • dp數(shù)組的含義:
    dp[i][0] 表示第i天持有股票所省最多現(xiàn)金。 dp[i][1] 表示第i天不持有股票所得最多現(xiàn)金

如果第i天持有股票即dp[i][0]饱狂, 那么可以由兩個(gè)狀態(tài)推出來

  • 第i-1天就持有股票曹步,那么就保持現(xiàn)狀,所得現(xiàn)金就是昨天持有股票的所得現(xiàn)金 即:dp[i - 1][0]
  • 第i天買入股票嗡官,所得現(xiàn)金就是昨天不持有股票的所得現(xiàn)金減去 今天的股票價(jià)格 即:dp[i - 1][1] - prices[i]

所以:dp[i][0] = max(dp[i - 1][0], 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)金纳猫,注意這里需要有手續(xù)費(fèi)了即:dp[i - 1][0] + prices[i] - fee

所以:dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee);

class Solution {
    public int maxProfit(int[] prices, int fee) {
        int[] dp = new int[2];
        // 0代表持有婆咸,1代表賣出
        dp[0] = -prices[0];
        dp[1] = 0;
        for(int i = 1; i <= prices.length; i++){ 
            //因?yàn)檠h(huán)是從1到len,所以實(shí)際上i=1芜辕,訪問的是prices[0],所以為了保證數(shù)組索引對(duì)齊尚骄,這里面都是prices[i-1]
            //區(qū)別于二維數(shù)組的解法,因?yàn)槎S數(shù)組的循環(huán)是從1到len-1侵续,使用i訪問prices[i]
            //第i-1天(這時(shí)候是用dp[0]保存的)就持有; 或第i天買入股票倔丈,所得現(xiàn)金就是昨天不持有股票的所得現(xiàn)金減去 今天的股票價(jià)格
            dp[0] = Math.max(dp[0], dp[1] - prices[i - 1]);
            //第i-1天就不持有;或第i天賣出股票状蜗,所得現(xiàn)金就是按照今天股票價(jià)格賣出再減去手續(xù)費(fèi)后所得現(xiàn)金
            dp[1] = Math.max(dp[1], dp[0] + prices[i - 1] - fee);
        }
        return dp[1]; //二維數(shù)組應(yīng)該返回length-1需五,但是這里改了for循環(huán)里 i <= prices.length
    }
}

股票總結(jié)

股票問題做一個(gè)總結(jié)吧

https://programmercarl.com/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92-%E8%82%A1%E7%A5%A8%E9%97%AE%E9%A2%98%E6%80%BB%E7%BB%93%E7%AF%87.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市轧坎,隨后出現(xiàn)的幾起案子宏邮,更是在濱河造成了極大的恐慌,老刑警劉巖缸血,帶你破解...
    沈念sama閱讀 221,820評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蜜氨,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡捎泻,警方通過查閱死者的電腦和手機(jī)飒炎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來笆豁,“玉大人郎汪,你說我怎么就攤上這事∮婧牵” “怎么了怒竿?”我有些...
    開封第一講書人閱讀 168,324評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)扩氢。 經(jīng)常有香客問我耕驰,道長(zhǎng),這世上最難降的妖魔是什么录豺? 我笑而不...
    開封第一講書人閱讀 59,714評(píng)論 1 297
  • 正文 為了忘掉前任朦肘,我火速辦了婚禮饭弓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘媒抠。我一直安慰自己弟断,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評(píng)論 6 397
  • 文/花漫 我一把揭開白布趴生。 她就那樣靜靜地躺著阀趴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪苍匆。 梳的紋絲不亂的頭發(fā)上刘急,一...
    開封第一講書人閱讀 52,328評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音浸踩,去河邊找鬼叔汁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛检碗,可吹牛的內(nèi)容都是我干的据块。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼折剃,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼另假!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起微驶,我...
    開封第一講書人閱讀 39,804評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤浪谴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后因苹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體苟耻,經(jīng)...
    沈念sama閱讀 46,345評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評(píng)論 3 340
  • 正文 我和宋清朗相戀三年扶檐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了凶杖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,561評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡款筑,死狀恐怖智蝠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情奈梳,我是刑警寧澤杈湾,帶...
    沈念sama閱讀 36,238評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站攘须,受9級(jí)特大地震影響漆撞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評(píng)論 3 334
  • 文/蒙蒙 一浮驳、第九天 我趴在偏房一處隱蔽的房頂上張望悍汛。 院中可真熱鬧,春花似錦至会、人聲如沸离咐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宵蛀。三九已至,卻和暖如春瓶蚂,著一層夾襖步出監(jiān)牢的瞬間糖埋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工窃这, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人征候。 一個(gè)月前我還...
    沈念sama閱讀 48,983評(píng)論 3 376
  • 正文 我出身青樓杭攻,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親疤坝。 傳聞我的和親對(duì)象是個(gè)殘疾皇子兆解,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評(píng)論 2 359

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