機模板精講)
訓練營第四十天的題單放在一起群里直接炸了188、309、714三題全是“買賣股票的最佳時機”系列的進階版。刷到這你會發(fā)現(xiàn)前面還在講貪心、講普通狀態(tài)轉(zhuǎn)移現(xiàn)在突然要同時處理交易次數(shù)、冷凍期、手續(xù)費三個額外條件。先別慌這三道題看著嚇人但本質(zhì)上共用同一套動態(tài)規(guī)劃模型——如果你搞懂了“股票狀態(tài)機”這個思路它們就是同一個模板換了三次參數(shù)。這篇就按我實際刷題時的順序來聊先解188把“最多k次交易”這個最通用的模型打通再上309看冷凍期是怎么在狀態(tài)圖里多卡一個“冷靜”節(jié)點最后是714手續(xù)費說白了就是在賣出時多扣一筆錢。順帶會把初始化、邊界條件、滾動數(shù)組這些容易被細節(jié)絆倒的地方全部攤開講。適合的人群很明確DP已經(jīng)入門、想系統(tǒng)吃透股票專題或者面試前準備動態(tài)規(guī)劃的讀者這篇可以直接當復習筆記用。1. 三道題放一起刷才能看懂股票DP的套路1.1 從“一次買賣”到“帶約束買賣”遞進關(guān)系在哪先盤一下這個系列在LeetCode上的完整梯度121只能買賣一次122可以無限次買賣123限制最多兩筆188把123推廣成最多k筆309在無限次基礎(chǔ)上加了冷凍期714在無限次基礎(chǔ)上加了手續(xù)費。這里有個很關(guān)鍵的認知121、122、123、188是在“交易次數(shù)”這個維度上遞進而309、714是在“交易規(guī)則”上做約束。前者考驗你對狀態(tài)維度的抽象能力——從1次擴展到2次就能勸退一批人擴展到k次更是讓很多人直接寫錯數(shù)組大小后者考驗你對“額外狀態(tài)”的敏感度——冷凍期本質(zhì)上是給空倉狀態(tài)再拆成“能買”和“不能買”兩種手續(xù)費則只是在利潤計算時多一個減法。把這些題放在同一天刷的價值就在這里你能清楚地看到所謂的“新題”并不是全新的解題思路而是在同一個狀態(tài)機上加點約束。先學會畫狀態(tài)轉(zhuǎn)移圖后面所有變體都是在圖里增加節(jié)點或修改邊權(quán)。1.2 股票DP的核心建模方式用狀態(tài)圖代替背公式做股票類DP我的習慣是永遠先問自己一句話每天交易結(jié)束后我可能處于哪幾種狀態(tài)而不是一上來就背轉(zhuǎn)移公式。為什么強調(diào)“結(jié)束后”因為股票交易是按天發(fā)生的你需要在第i天做決策買、賣、還是什么都不干。如果定義成“第i天操作完成后”的狀態(tài)那第i天買入的收益就只依賴第i-1天的狀態(tài)天然規(guī)避了“今天買入今天賣出”這種沒有意義的閉環(huán)。這個時間點的選擇決定了后面所有轉(zhuǎn)移方程是否干凈。用生活話來說你每天早上手里有一筆現(xiàn)金和一筆股票倉位一天結(jié)束時你的資產(chǎn)組合變成什么樣子取決于你今天的操作。dp數(shù)組記錄的就是在不同狀態(tài)下能拿到的最大利潤。狀態(tài)有多少種取決于題目給了多少約束有沒有交易次數(shù)限制有沒有冷卻期有沒有手續(xù)費。股票DP的所有公式都只是這個狀態(tài)圖在不同約束下的投影。1.3 為什么這類題刷一道沒用要三題連刷單刷188你可能學會了一個“奇數(shù)持有、偶數(shù)空倉”的數(shù)組技巧但腦子里的模型還是散的只刷309你記住了“賣出后要冷凍一天”但沒意識到這只是狀態(tài)圖里多了一個節(jié)點。三題連在一起你會發(fā)現(xiàn)狀態(tài)轉(zhuǎn)移這個事是有肌肉記憶的畫狀態(tài)、寫轉(zhuǎn)移、定初始、算答案永遠是這四步。更重要的是面試的時候面試官很喜歡在這個系列上做組合變化。今天考冷凍期明天可能問“手續(xù)費改成分段計費”后天可能問“最多k筆且?guī)Ю鋬銎凇薄H绻阒皇潜尺^某一道題的代碼遇到組合題就廢了但如果你腦子里裝的是“狀態(tài)機”這個框架任何新約束都只是在圖上加一筆的事。這也是為什么我強烈建議把這三題當成一個整體來學而不是零散地刷。2. 188題k次交易核心是把狀態(tài)數(shù)組“復制兩份”2.1 狀態(tài)定義把k筆交易拆成2k個狀態(tài)188題“買賣股票的最佳時機IV”題目要求最多完成k筆交易。123題是k2的特例當時用4個狀態(tài)還能勉強手寫擴展到k就必須建立通用的狀態(tài)編號。我的定義方式是這樣用一維狀態(tài)下標j表示當前處于“第幾筆交易的什么階段”其中j 0空倉還沒做過任何交易j 1持有第一筆股票j 2完成第一筆交易空倉j 3持有第二筆股票j 4完成第二筆交易空倉...規(guī)律很明顯奇數(shù)下標代表“持有中”偶數(shù)下標代表“空倉中”。總共有2k1個狀態(tài)下標從0到2k。為什么要多一個0因為它表示“從未買入”的初始狀態(tài)這個狀態(tài)在轉(zhuǎn)移中非常重要確保第1筆買入的金額不會被錯誤累積。持有狀態(tài)和空倉狀態(tài)交替出現(xiàn)每次買入讓下標加1每次賣出也讓下標加1。這樣寫代碼的時候只需要判斷j的奇偶性就能確定該用哪條轉(zhuǎn)移規(guī)則非常規(guī)整。2.2 轉(zhuǎn)移方程與完整代碼奇數(shù)持有、偶數(shù)空倉有了狀態(tài)編號轉(zhuǎn)移方程就順理成章了。對于第i天、狀態(tài)j如果j是偶數(shù)空倉今天可以繼續(xù)空倉也可以從“持有狀態(tài)”賣出。所以 dp[i][j] max(dp[i-1][j], dp[i-1][j-1] prices[i])如果j是奇數(shù)持有今天可以繼續(xù)持有也可以從“空倉狀態(tài)”買入。所以 dp[i][j] max(dp[i-1][j], dp[i-1][j-1] - prices[i])唯一需要注意的是j-1不能越界。j0時沒有上一個狀態(tài)它只能繼續(xù)空倉所以偶數(shù)狀態(tài)的j0才執(zhí)行買賣邏輯。完整代碼如下class Solution { public: int maxProfit(int k, vectorint prices) { int n prices.size(); if (n 0 || k 0) return 0; // 一筆完整交易至少需要兩天實際有效交易次數(shù)不會超過 n / 2 k min(k, n / 2); // 0 ~ 2*k 一共 2*k1 個狀態(tài) vectorvectorint dp(n, vectorint(2 * k 1, 0)); // 第0天所有奇數(shù)狀態(tài)持有初始化為 -prices[0] for (int j 1; j 2 * k; j 2) { dp[0][j] -prices[0]; } for (int i 1; i n; i) { for (int j 0; j 2 * k; j) { if (j % 2 0) { // 空倉狀態(tài) dp[i][j] dp[i - 1][j]; if (j 0) { dp[i][j] max(dp[i][j], dp[i - 1][j - 1] prices[i]); } } else { // 持有狀態(tài) dp[i][j] max(dp[i - 1][j], dp[i - 1][j - 1] - prices[i]); } } } // 最終答案完成最后一筆交易后的空倉狀態(tài) return dp[n - 1][2 * k]; } };這個寫法我在本地跑過官方測試用例和題解預期完全一致。核心就是理解偶數(shù)狀態(tài)用“賣出”轉(zhuǎn)入奇數(shù)狀態(tài)用“買入”轉(zhuǎn)入兩個方向?qū)獌煞N操作。2.3 k大于n/2時為什么可以先降級處理很多人一開始不注意k的取值范圍直接把數(shù)組開成2*k1。如果k很大比如10萬而prices只有3天這就會創(chuàng)建20萬列純屬浪費。一個簡單的數(shù)學結(jié)論一筆完整的交易至少需要兩天一天買入一天賣出所以n天最多完成n/2筆交易。只要k大于等于n/2實際約束就失效了等價于122題的無限次交易。處理方式就是在dp之前先做一次降級k min(k, n / 2);這樣數(shù)組大小始終可控。但要注意k0時要單獨返回0否則循環(huán)里會創(chuàng)建只有1列的數(shù)組邏輯上雖然沒錯但沒必要。2.4 初始化容易翻車第0天所有持有狀態(tài)怎么填第0天的初始化是188題最容易寫錯的地方。很多人的第一反應是dp[0][1] -prices[0]其他持有狀態(tài)都應該是極小值。但在代碼隨想錄的標準寫法里所有奇數(shù)狀態(tài)都直接初始化為-prices[0]。為什么這樣也能對因為在第0天買入第一筆的最優(yōu)利潤就是-prices[0]。對于“持有第二筆”如果從第1天開始轉(zhuǎn)移它會被dp[0][2]買入得到而dp[0][2]此時繼承的是0所以dp[1][3] max(dp[0][3], dp[0][2] - prices[1]) max(-prices[0], -prices[1])。也就是說“持有第二筆”的初始資金來自“完成第一筆”后的空倉利潤0再買入第二筆——0 - prices[1]是合法的。此時dp[0][3]-prices[0]雖然從字面上看是“第0天買了第二筆”但在max運算里它不會優(yōu)于未來真實的買入操作所以不影響最終結(jié)果。如果還是覺得別扭你可以把所有奇數(shù)狀態(tài)初始化成 INT_MIN / 2然后在轉(zhuǎn)移時跳過非法值。但對面試來說寫-prices[0]更簡潔而且只要理解了上面這個“不變壞”的道理就不會被面試官問倒。3. 309題冷凍期只卡“買入”一條路3.1 冷凍期到底為什么難309題“最佳買賣股票時機含冷凍期”規(guī)則是賣出股票后的第二天不能買入。也就是說今天賣出明天處于冷卻狀態(tài)不能買后天才能重新買入。難點在于兩狀態(tài)DP持有/不持有在冷凍期規(guī)則下不夠用了。因為“不持有”有兩種完全不同的情況——一種是可以自由買入一種是昨天剛賣完、今天被迫冷靜。這兩種狀態(tài)對未來決策的影響不同可買入狀態(tài)能直接買冷靜狀態(tài)必須再多等一天。所以必須把“不持有”拆成兩個狀態(tài)。這也是狀態(tài)機思維的價值遇到新約束先問自己“原有的狀態(tài)分類是否足夠表達當前規(guī)則”。不夠就拆。3.2 三狀態(tài)轉(zhuǎn)移公式與代碼我用三個狀態(tài)來表示第i天結(jié)束后的情況狀態(tài)0持有股票狀態(tài)1不持有股票且處于冷凍期也就是今天剛賣出狀態(tài)2不持有股票且不在冷凍期可以自由買入轉(zhuǎn)移邏輯如下狀態(tài)0持有今天繼續(xù)持有或者今天從“可自由買入”狀態(tài)買入。注意買入只能從狀態(tài)2來不能從狀態(tài)1來因為狀態(tài)1是冷凍期不能買。 dp[i][0] max(dp[i-1][0], dp[i-1][2] - prices[i])狀態(tài)1冷凍期今天不持有且冷凍只可能是昨天持有今天賣出。 dp[i][1] dp[i-1][0] prices[i]狀態(tài)2可買入空倉今天不持有也不冷凍可能是昨天就處于冷凍期、今天解凍了也可能昨天本來就是可買入空倉。 dp[i][2] max(dp[i-1][1], dp[i-1][2])代碼class Solution { public: int maxProfit(vectorint prices) { int n prices.size(); if (n 0) return 0; // 0: 持有 1: 空倉且冷凍 2: 空倉且可買 vectorvectorint dp(n, vectorint(3, 0)); dp[0][0] -prices[0]; dp[0][1] 0; dp[0][2] 0; for (int i 1; i n; i) { dp[i][0] max(dp[i - 1][0], dp[i - 1][2] - prices[i]); dp[i][1] dp[i - 1][0] prices[i]; dp[i][2] max(dp[i - 1][1], dp[i - 1][2]); } // 最后一天持有不如賣出所以答案在狀態(tài)1和狀態(tài)2里 return max(dp[n - 1][1], dp[n - 1][2]); } };用官方示例prices [1,2,3,0,2]跑一遍結(jié)果是3最優(yōu)路徑是第0天買入、第1天賣出賺1然后等第3天買入、第4天賣出賺2。中間第2天處于冷凍期不能買這個例子把規(guī)則展示得非常直觀。3.3 狀態(tài)壓縮要注意求值順序二維數(shù)組寫對了之后很多人想優(yōu)化成幾個變量。這時候就會踩一個經(jīng)典坑直接原地更新后面的狀態(tài)用了被覆蓋過的舊值。比如有人寫成// 錯誤示范 for (int i 1; i n; i) { dp0 max(dp0, dp2 - prices[i]); dp1 dp0_old prices[i]; // 這里的 dp0 已經(jīng)被更新了 dp2 max(dp1_old, dp2); }問題在于計算dp1時需要的是前一天持有狀態(tài)的舊值但dp0已經(jīng)被今天的值覆蓋了計算dp2時需要的是昨天冷凍狀態(tài)的舊值但dp1可能剛被覆蓋。結(jié)果整個鏈條串味。正確做法是先緩存舊值int hold -prices[0]; // 持有 int cool 0; // 空倉且冷凍 int rest 0; // 空倉且可買 for (int i 1; i n; i) { int preHold hold, preCool cool, preRest rest; hold max(preHold, preRest - prices[i]); cool preHold prices[i]; rest max(preCool, preRest); } return max(cool, rest);這里每一步用的都是“前一天”的值順序就不再影響正確性。這個坑在面試手寫代碼時特別容易暴露我建議刷題階段就把緩存舊值的習慣養(yǎng)好。3.4 冷凍期加上交易次數(shù)限制怎么擴展思路如果面試官在309基礎(chǔ)上追問一句“最多k筆且?guī)Ю鋬銎凇辈灰?。思路是讓狀態(tài)多一個交易次數(shù)的維度dp[i][k][0]表示經(jīng)歷過k筆交易后持有dp[i][k][1]表示經(jīng)歷過k筆交易后空倉且冷凍dp[i][k][2]表示空倉且可買。轉(zhuǎn)移規(guī)則幾乎不變只是買入和賣出時把k的計數(shù)變化寫清楚。狀態(tài)圖還是那張狀態(tài)圖只是從二維變成了三維。這就是狀態(tài)機模型的可擴展性。4. 714題手續(xù)費本質(zhì)是給“賣出”加負擔4.1 手續(xù)費放買入還是賣出都行但必須一致714題“買賣股票的最佳時機含手續(xù)費”每次交易要付固定手續(xù)費fee。核心決策點只有一個手續(xù)費什么時候從利潤里扣除。兩種主流寫法賣出時扣費買入時只花prices[i]賣出時到賬prices[i] - fee。買入時扣費買入時多花fee賣出時正常到賬prices[i]。兩種寫法最終結(jié)果完全一樣因為每筆交易只會扣一次費用扣在利潤的哪一端不影響凈收益。但一定要保持一致不能這邊初始化按買入扣費那邊轉(zhuǎn)移又在賣出扣一次那就等于扣了兩次。我習慣用“賣出時扣費”因為更符合直覺買入就是資金流出賣出就是資金流入手續(xù)費在流入時直接扣除不容易漏。4.2 賣出扣費版本的完整代碼class Solution { public: int maxProfit(vectorint prices, int fee) { int n prices.size(); if (n 0) return 0; // 0: 空倉 1: 持有 vectorvectorint dp(n, vectorint(2, 0)); dp[0][0] 0; dp[0][1] -prices[0]; for (int i 1; i n; i) { // 空倉繼續(xù)空倉或者從持有狀態(tài)賣出并扣手續(xù)費 dp[i][0] max(dp[i - 1][0], dp[i - 1][1] prices[i] - fee); // 持有繼續(xù)持有或者從空倉狀態(tài)買入 dp[i][1] max(dp[i - 1][1], dp[i - 1][0] - prices[i]); } return dp[n - 1][0]; } };如果選買入時扣費初始化和轉(zhuǎn)移改成dp[0][1] -prices[0] - fee; dp[i][0] max(dp[i - 1][0], dp[i - 1][1] prices[i]); dp[i][1] max(dp[i - 1][1], dp[i - 1][0] - prices[i] - fee);只要保證“每一筆交易只在買入或賣出其中一個環(huán)節(jié)扣費”結(jié)果就不會有偏差。4.3 大手續(xù)費時為什么也能自動“不交易”有一個非常容易忽略的細節(jié)如果fee設(shè)置得很大比如prices每天波動只有1塊但手續(xù)費要10塊最優(yōu)策略是干脆不做任何交易。這個行為不需要特判DP會自然給出0。原因在于dp[i][0] max(dp[i-1][0], ...)里的dp[i-1][0]它代表“之前一直空倉”的利潤0。只要每次賣出的凈利潤是負的max就會選擇繼續(xù)空倉最終答案回到0。我拿prices[1,5,2,8], fee3跑過最優(yōu)是先1買5賣賺4-31再2買8賣賺6-33總4。如果只看局部第一筆1買2賣利潤是-1會被DP自動跳過。這種“自動跳過虧損交易”的特性是股票DP和貪心的一個重要區(qū)別也解釋了為什么這類題用DP比貪心更穩(wěn)。4.4 變體討論雙邊收費怎么辦如果題目改成“買入和賣出各收一次手續(xù)費”本質(zhì)上等價于每筆交易支付2fee。你只需要把賣出扣的fee改成2fee或者把買入扣和賣出扣同時保留邏輯完全一致。理解了這個等價關(guān)系面試時不管手續(xù)費怎么包裝你都能立刻轉(zhuǎn)化成已知模型。5. 三題連刷的復盤我發(fā)現(xiàn)股票DP的通用套路5.1 第一步永遠先畫狀態(tài)轉(zhuǎn)移圖我發(fā)現(xiàn)無論題目怎么變動手寫代碼前花兩分鐘把狀態(tài)圖畫出來比直接背公式高效得多。所謂“狀態(tài)轉(zhuǎn)移圖”就是用箭頭表示“今天結(jié)束時的狀態(tài)A經(jīng)過什么操作可以變成明天結(jié)束時的狀態(tài)B”。以309為例三狀態(tài)的完整轉(zhuǎn)移關(guān)系是持有態(tài)可以什么都不做繼續(xù)持有也可以賣出進入冷凍空倉態(tài)。冷凍態(tài)什么都不做下一天變成可買入空倉態(tài)??少I入空倉態(tài)可以什么都不做繼續(xù)空倉也可以買入進入持有態(tài)。把這個圖畫出來轉(zhuǎn)移方程就是照著箭頭寫的根本不需要死記。188的狀態(tài)圖則是一串交替的持有/空倉節(jié)點買入和賣出就是沿著這串節(jié)點向前走。5.2 第二步明確“當天結(jié)束后”的時間點所有狀態(tài)定義都要統(tǒng)一到“第i天結(jié)束后”。這樣第i天的操作只依賴第i-1天結(jié)束時的狀態(tài)不會出現(xiàn)同一天內(nèi)先買后賣、先賣后買的混亂。有人喜歡定義成“第i天操作前”也可以但轉(zhuǎn)移方程會多出不少邊界判斷。統(tǒng)一用“結(jié)束后”這個時間點代碼最干凈復盤時也最容易對照狀態(tài)圖。5.3 第三步判斷狀態(tài)維度能否壓縮188的狀態(tài)數(shù)隨k線性增長309和714則只需要3個或2個狀態(tài)。能壓縮的題目通常有一個共同特征第i天只依賴第i-1天的值不需要更早的歷史。這時用幾個變量或兩行數(shù)組滾動即可。但我給個實際建議刷題初期先老老實實寫二維數(shù)組把邏輯跑通滾動數(shù)組作為進階優(yōu)化后續(xù)再做。因為滾動數(shù)組踩的坑舊值覆蓋比二維數(shù)組多得多尤其是309那道題搞錯求值順序直接就是WA排查起來還不好找。5.4 面試時怎么快速講清楚這類DP如果面試現(xiàn)場遇到股票類DP我的講解順序是固定的先定義狀態(tài)“dp[i][j]表示第i天結(jié)束后處于狀態(tài)j的最大利潤。狀態(tài)j分別代表……”然后列出狀態(tài)圖“持有態(tài)可以從……轉(zhuǎn)移來空倉態(tài)可以從……轉(zhuǎn)移來?!弊詈笳f初始化“第0天的合法操作決定了初值第0天買入就是-prices[0]不操作就是0?!睆碗s度直接報O(n狀態(tài)數(shù))狀態(tài)數(shù)通常只有2到3個188則是O(nk)。這樣講解面試官能立刻看出你是真懂還是背題。尤其是188能說清楚“奇數(shù)狀態(tài)從空倉買入、偶數(shù)狀態(tài)從持有賣出”這個規(guī)律比直接甩代碼要有說服力得多。6. 踩坑記錄刷這三題時最容易犯的錯6.1 常見問題速查表刷完這三題我把容易翻車的點整理成了一張速查表題目典型錯誤原因解決辦法188數(shù)組大小寫成2k而不是2k1忘了狀態(tài)0表示“從未交易”下標0~2k長度2k1188k沒有min(k, n/2)超大k導致內(nèi)存爆炸沒意識到完整交易至少兩天dp前先做k min(k, n/2)188第0天奇數(shù)狀態(tài)全部用極小值把第二筆交易的“買入”初始化為不合法用-prices[0]初始化所有奇數(shù)狀態(tài)309狀態(tài)壓縮時直接原地更新新值覆蓋了舊值導致后一個狀態(tài)拿到“今天”的數(shù)據(jù)先緩存前一天三個狀態(tài)再更新309狀態(tài)1和狀態(tài)2的轉(zhuǎn)移寫反沒搞清楚“今天賣出當天算冷凍”狀態(tài)1只由“昨天持有今天賣出”產(chǎn)生714買入扣費和賣出扣費混用手續(xù)費扣了兩次或漏扣統(tǒng)一只在一端扣費初始化與之對應714不交易時答案變成負數(shù)手續(xù)費大于利潤時被迫賣出dp[i][0]從dp[i-1][0]繼承天然規(guī)避6.2 我自己的刷題習慣我在刷這三題時習慣是先把狀態(tài)轉(zhuǎn)移圖寫在紙上再對照官方題解看自己的狀態(tài)分類和題解是否一致。如果一致再自己寫代碼如果不一致我會先想明白為什么題解這么分而不是直接抄代碼。三題連刷下來我最大的感受是股票DP真正難的不是轉(zhuǎn)移公式而是“狀態(tài)劃分”這一步。狀態(tài)劃對了公式自己就會冒出來狀態(tài)劃錯后面全亂。6.3 給新手的兩個自測小技巧一個是在LeetCode提交前先用小的樣例手算一遍。比如309用[1,2,3,0,2]這類官方示例714用[1,3,2,8,4,9], fee2這類帶手續(xù)費但仍有利潤的用例。如果答案能跟手算對上代碼大概率沒問題。另一個是檢查邊界n0或n1要直接返回0k0也要返回0。很多WA不是因為轉(zhuǎn)移方程錯而是邊界條件沒寫。把這個習慣固化下來之后刷其他DP題也一樣受益。7. 訓練營第四十天我的刷題節(jié)奏與筆記整理除了這三道題本身我也想聊聊怎么在一個訓練營的節(jié)奏里把它們真正消化掉。代碼隨想錄的題單是把同一專題的題集中排布所以第四十天其實是最好的“歸納整理”時機。我的做法是上午先不看題解自己嘗試寫188卡住了就回去翻123的兩筆交易寫法找“從2到k”的共性下午做309和714重點對比它們和122的差異。晚上把三道題的狀態(tài)定義、轉(zhuǎn)移方程、邊界條件抄在同一頁筆記上用表格橫向比較。這頁筆記后來在我復習時幫了大忙比反復刷題效率高得多。對于第一次接觸股票DP的讀者我的建議是不要試圖一天內(nèi)完全理解所有細節(jié)。先能獨立寫出188的二維DP再給309加上第三個狀態(tài)最后給714加上手續(xù)費每一步都跑通官方用例。這樣拆開練第四十天這個節(jié)點才能真正把“股票系列”轉(zhuǎn)化為自己的東西。最后分享一個小技巧把三道題的狀態(tài)定義壓縮成一句話——“先想清楚每天交易結(jié)束后有哪幾種狀態(tài)再畫箭頭最后寫轉(zhuǎn)移公式”。這句口訣我后來在面試里用過好幾次都能很快理清思路。股票DP并沒有傳說中那么玄它只是把“狀態(tài)復用”這件事做到了極致而已。