【算法-LeetCode】121. 买卖股票的最佳时机(动态规划;贪心)
121. 買(mǎi)賣(mài)股票的最佳時(shí)機(jī) - 力扣(LeetCode)
發(fā)布:2021年9月15日13:44:26
問(wèn)題描述及示例
給定一個(gè)數(shù)組 prices ,它的第 i 個(gè)元素 prices[i] 表示一支給定股票第 i 天的價(jià)格。
你只能選擇 某一天 買(mǎi)入這只股票,并選擇在 未來(lái)的某一個(gè)不同的日子 賣(mài)出該股票。設(shè)計(jì)一個(gè)算法來(lái)計(jì)算你所能獲取的最大利潤(rùn)。
返回你可以從這筆交易中獲取的最大利潤(rùn)。如果你不能獲取任何利潤(rùn),返回 0 。
示例 1:
輸入:[7,1,5,3,6,4]
輸出:5
解釋:在第 2 天(股票價(jià)格 = 1)的時(shí)候買(mǎi)入,在第 5 天(股票價(jià)格 = 6)的時(shí)候賣(mài)出,最大利潤(rùn) = 6-1 = 5 。
注意利潤(rùn)不能是 7-1 = 6, 因?yàn)橘u(mài)出價(jià)格需要大于買(mǎi)入價(jià)格;同時(shí),你不能在買(mǎi)入前賣(mài)出股票。
示例 2:
輸入:prices = [7,6,4,3,1]
輸出:0
解釋:在這種情況下, 沒(méi)有交易完成, 所以最大利潤(rùn)為 0。
來(lái)源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock
著作權(quán)歸領(lǐng)扣網(wǎng)絡(luò)所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系官方授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。
提示:
1 <= prices.length <= 105
0 <= prices[i] <= 104
我的題解
更新:2021年10月15日17:26:39
又寫(xiě)了一道股票問(wèn)題的題解,本篇博客中的代碼沒(méi)有相關(guān)的注釋,而下面參考博客中有我的詳細(xì)解釋,可以作為參考:
參考:【算法-LeetCode】122. 買(mǎi)賣(mài)股票的最佳時(shí)機(jī) II(動(dòng)態(tài)規(guī)劃;貪心)_賴念安的博客-CSDN博客
【更新結(jié)束】
我的題解1(暴力解法—因超時(shí)而無(wú)法通過(guò))
這個(gè)思路還是很直接的,就是遍歷 prices 數(shù)組,然后在遍歷過(guò)程中再進(jìn)行內(nèi)部遍歷,假設(shè)外層遍歷到了 i,那內(nèi)層遍歷就是由 0 到 i-1。在遍歷過(guò)程中用 profit 記錄利潤(rùn)的歷史最大值。由于有兩層嵌套的遍歷,這就導(dǎo)致程序的時(shí)間復(fù)雜度為O(n2)O(n^2)O(n2),當(dāng)遇到比較大的數(shù)據(jù)量時(shí),程序運(yùn)行時(shí)間就會(huì)超出時(shí)間限制。
/*** @param {number[]} prices* @return {number}*/ var maxProfit = function(prices) {// profit用于動(dòng)態(tài)存儲(chǔ)利潤(rùn)最大值,遍歷完成后,其值就是我們想要的結(jié)果let profit = 0;// 因?yàn)椴荒茉偻惶熨I(mǎi)賣(mài)股票獲得利潤(rùn),所以當(dāng)prices的長(zhǎng)度為1時(shí)應(yīng)當(dāng)直接返回0。if(prices.length === 1) {return 0;}// 開(kāi)始遍歷prices數(shù)組,sell是我們假設(shè)賣(mài)出股票的日期for(let sell = 1; sell < prices.length; sell++) {// buy是我們假設(shè)買(mǎi)入股票的日期,買(mǎi)入日一定是在賣(mài)出日之前的for(let buy = 0; buy < sell; buy++) {// 若假設(shè)的賣(mài)出日的股票價(jià)格不比買(mǎi)入日的股票價(jià)格高,// 那么說(shuō)明這種買(mǎi)賣(mài)方案的利潤(rùn)不為正,可以直接結(jié)束本次內(nèi)層遍歷if(prices[sell] <= prices[buy]) {continue;}// 如果有利可圖,那么就計(jì)算當(dāng)前買(mǎi)賣(mài)方案所能得到的利潤(rùn),// 與歷史最大利潤(rùn)做比較,并把profit更新為較大的那個(gè)值profit = Math.max(profit, prices[sell] - prices[buy]);}}// 遍歷完成后,profit就是所能得到的最大利潤(rùn)return profit; };提交記錄 201 / 211 個(gè)通過(guò)測(cè)試用例 狀態(tài):超出時(shí)間限制 時(shí)間:2021/09/14 21:42
很可惜,這種解法確實(shí)是很好理解,但是碰到超長(zhǎng)的測(cè)試用例是還是力不從心……
我的題解2(動(dòng)態(tài)規(guī)劃—失敗)
有關(guān)動(dòng)態(tài)規(guī)劃的解法,我之前寫(xiě)了相關(guān)的總結(jié)步驟,可以參考下面的博客:
參考:【算法-LeetCode】53. 最大子序和(動(dòng)態(tài)規(guī)劃初體驗(yàn))_賴念安的博客-CSDN博客
因?yàn)橥得榈健咎崾尽坷镉小皠?dòng)態(tài)規(guī)劃”的標(biāo)簽,所以,我就開(kāi)始往這個(gè)角度思考。寫(xiě)出了下面這個(gè)看起來(lái)很不像動(dòng)態(tài)規(guī)劃的動(dòng)態(tài)規(guī)劃解法。
我這里的 dp 數(shù)組的含義是:dp[i] 表示第 i 天所能獲得的最大利潤(rùn)。而指針 p 則用于記錄當(dāng)前獲得最大利潤(rùn)時(shí)的日期下標(biāo)。
程序本質(zhì)還是在尋找一段股價(jià)總體上升的區(qū)間的。
程序所匹配的目標(biāo)股價(jià)區(qū)間/*** @param {number[]} prices* @return {number}*/ var maxProfit = function (prices) {// 創(chuàng)建長(zhǎng)度和prices數(shù)組相同的dp數(shù)組,并默認(rèn)填充0,這在后面的判斷中會(huì)用上let dp = Array.from({ length: prices.length }).fill(0);// 初始化dpdp[0] = 0;// p指針用于標(biāo)識(shí)當(dāng)前獲得最大利潤(rùn)時(shí)的那個(gè)賣(mài)出日let p = 0;for (let i = 1; i < prices.length; i++) {// 如果當(dāng)前股價(jià)大于此前所能獲取最大利潤(rùn)時(shí)的股價(jià),則說(shuō)明我現(xiàn)在賣(mài)出股票能獲取更多的利潤(rùn)if (prices[i] > prices[p]) {// 如果當(dāng)前賣(mài)出能夠獲取更多利潤(rùn),那么當(dāng)前能夠獲得利潤(rùn)就是在之前的最大利潤(rùn)基礎(chǔ)上// 加上當(dāng)前股價(jià)與此前獲得最大利潤(rùn)時(shí)的股價(jià)的差價(jià)dp[i] = dp[p] + prices[i] - prices[p];// 更新p指針的指向,標(biāo)識(shí)目前第i天賣(mài)出股票所能獲得的利潤(rùn)最大p = i;// 如果已經(jīng)完成了p指針的更新,那么后續(xù)的操作就不需要進(jìn)行了,直接結(jié)束本次循環(huán)continue;}// 如果現(xiàn)在賣(mài)出不能獲得更大的利潤(rùn),則進(jìn)入下一次循環(huán),此前需要判斷p指針是否需要移動(dòng)// 這里判斷dp[p]是否是初始狀態(tài)是為了讓?xiě)?yīng)對(duì)還未遇到下降的股價(jià)時(shí)的情況if (dp[p] === 0) {p++;}}// 最后,在p指針?biāo)赶虻娜掌谫u(mài)出股票能獲得最大利潤(rùn),將其返回return dp[p]; };這次我沒(méi)有點(diǎn)擊提交,畢竟提交后也是不通過(guò)的
然而很不幸的是,雖然內(nèi)置的兩個(gè)小測(cè)試用例能夠通過(guò)測(cè)試,但是那個(gè)超長(zhǎng)測(cè)試用例還是沒(méi)有通過(guò),這次不是超時(shí),而是壓根就是邏輯錯(cuò)誤。
上面的那個(gè)超長(zhǎng)用例的運(yùn)行結(jié)果(未通過(guò))
于是,我就開(kāi)始思考為什么沒(méi)有通過(guò)……后來(lái)突然想明白了,我這種解法就是默認(rèn)了 prices 中第一次遇到的最小值(也就是下圖中的A點(diǎn))就是整個(gè)數(shù)組的最小值了(也即是我們心里預(yù)期的那個(gè)最佳的股票買(mǎi)入日的股價(jià))。可事實(shí)上,也許后續(xù)還有可能出現(xiàn)比A點(diǎn)價(jià)格更低的股價(jià)(比如下面的點(diǎn)B)。由下面的圖可以看到,很顯然,在B點(diǎn)買(mǎi)入才能讓后期賣(mài)出股票時(shí)利潤(rùn)最大。
我的題解3(動(dòng)態(tài)規(guī)劃—成功)
嚴(yán)格來(lái)說(shuō),這個(gè)解法不是我想出來(lái)的,在此感謝分享題解的博主和題友。
下面這種動(dòng)態(tài)規(guī)劃的解法主要還是參考以下兩位博主的思路:
參考:暴力解法、動(dòng)態(tài)規(guī)劃(Java) - 買(mǎi)賣(mài)股票的最佳時(shí)機(jī) - 力扣(LeetCode)
參考: 「代碼隨想錄」帶你學(xué)透股票系列!121. 買(mǎi)賣(mài)股票的最佳時(shí)機(jī)【暴力】【貪心】【動(dòng)態(tài)規(guī)劃】詳解 - 買(mǎi)賣(mài)股票的最佳時(shí)機(jī) - 力扣(LeetCode)
可以看到,這種解法只需要遍歷一次數(shù)組,沒(méi)有雙層嵌套的 for 循環(huán)。這是二維數(shù)組的解法,當(dāng)然還是可以像我之前做的滾動(dòng)數(shù)組那樣通過(guò)滾動(dòng)數(shù)組的方式來(lái)降低空間消耗。這在上面提到的參考題解中也有提到,思路還是和之前提到的差不多,這里就不再敘述了, 畢竟?jié)L動(dòng)數(shù)組確實(shí)不好描述。
詳細(xì)的解釋在我上面提到的兩個(gè)參考題解中都有非常詳細(xì)的解釋,我這里就不再重復(fù)了。總的來(lái)說(shuō)還是動(dòng)態(tài)規(guī)劃的那幾個(gè)流程,我之所以沒(méi)有想到這種二維數(shù)組的解法,就是在第一步確定 dp 數(shù)組的含義時(shí)想歪了,本題中最關(guān)鍵的地方就在于此,只要明白了這個(gè)點(diǎn),后面的推導(dǎo)就水到渠成了。不得不說(shuō)雖然動(dòng)態(tài)規(guī)劃的解題特征比較清晰,但是對(duì)其的應(yīng)用確實(shí)千變?nèi)f化的……
當(dāng)然,在【官方題解】中,也有一種看起來(lái)形式不太相同的動(dòng)態(tài)規(guī)劃解法,其評(píng)論區(qū)里也有題友提出了一些比較好的理解思路,非常值得參考。
我的題解3(貪心)
在相關(guān)的題解中,也有一種利用貪心的解法:
/*** @param {number[]} prices* @return {number}*/ var maxProfit = function (prices) {if(prices.length === 1) {return 0;}let profit = 0;let minPrice = Infinity;for(let i = 0; i < prices.length; i++) {minPrice = Math.min(minPrice, prices[i]);profit = Math.max(profit, prices[i] - minPrice);}return profit; };提交記錄 211 / 211 個(gè)通過(guò)測(cè)試用例 狀態(tài):通過(guò) 執(zhí)行用時(shí):96 ms, 在所有 JavaScript 提交中擊敗了67.27%的用戶 內(nèi)存消耗:47.3 MB, 在所有 JavaScript 提交中擊敗了95.02%的用戶 時(shí)間:2021/09/17 16:53總體思路就是在遍歷 prices 的過(guò)程中,用一個(gè) minPrices 存儲(chǔ)所遇到的最低股價(jià),用 profit 存儲(chǔ)目前能獲取的最大利潤(rùn),而且這個(gè)最大利潤(rùn)就是遍歷過(guò)程中所獲取的最大股價(jià)差。其實(shí)這種方式我和我上面自己寫(xiě)的那個(gè)失敗的動(dòng)態(tài)規(guī)劃有點(diǎn)相似的味道。不過(guò)我的那個(gè)只能適應(yīng)一小部分情況。
這段時(shí)間感覺(jué)做題的節(jié)奏沒(méi)能保持下去,主要還是一對(duì)亂七八糟的東西要處理,還有一些必要的技能要學(xué),總的來(lái)說(shuō)還是有點(diǎn)怠惰了。這篇題解記錄就不做詳細(xì)的逐句注釋了,因?yàn)橹饕€是看別人的題解,而且別人的題解也已經(jīng)講得很清楚了。到時(shí)候回顧的時(shí)候應(yīng)該也沒(méi)啥大問(wèn)題吧。
官方題解
更新:2021年7月29日18:43:21
因?yàn)槲铱紤]到著作權(quán)歸屬問(wèn)題,所以【官方題解】部分我不再粘貼具體的代碼了,可到下方的鏈接中查看。
更新:2021年9月15日13:49:21
參考:買(mǎi)賣(mài)股票的最佳時(shí)機(jī) - 買(mǎi)賣(mài)股票的最佳時(shí)機(jī) - 力扣(LeetCode)
【更新結(jié)束】
有關(guān)參考
更新:2021年9月16日22:38:13
參考:暴力解法、動(dòng)態(tài)規(guī)劃(Java) - 買(mǎi)賣(mài)股票的最佳時(shí)機(jī) - 力扣(LeetCode)
參考: 「代碼隨想錄」帶你學(xué)透股票系列!121. 買(mǎi)賣(mài)股票的最佳時(shí)機(jī)【暴力】【貪心】【動(dòng)態(tài)規(guī)劃】詳解 - 買(mǎi)賣(mài)股票的最佳時(shí)機(jī) - 力扣(LeetCode)
更新:2021年9月17日17:30:57
參考:【算法-LeetCode】53. 最大子序和(動(dòng)態(tài)規(guī)劃初體驗(yàn))_賴念安的博客-CSDN博客
更新:2021年10月15日17:26:39
參考:【算法-LeetCode】122. 買(mǎi)賣(mài)股票的最佳時(shí)機(jī) II(動(dòng)態(tài)規(guī)劃;貪心)_賴念安的博客-CSDN博客
后言
更新:2021年9月19日11:34:05
昨天睡覺(jué)前看了下自己的CSDN博客消息,發(fā)現(xiàn)有人給我這篇博客點(diǎn)贊,其中還有一位關(guān)注了我,我的粉絲數(shù)也從5變?yōu)榱?,雖說(shuō)談不上多么興奮,因?yàn)槲业牟┛蛢?nèi)容更多地是用于自己回顧的,沒(méi)想著能真正幫到其他人。但是總歸是自己的內(nèi)容收到了其他人的肯定,還是非常感謝的~
昨晚收到的點(diǎn)贊消息昨晚關(guān)注我的用戶
說(shuō)實(shí)話,這篇博客我倒覺(jué)得寫(xiě)得粗制濫造,因?yàn)橹拔业念}解博客中基本都是有逐行的解釋過(guò)程的,但是這篇博客卻沒(méi)有,尤其是關(guān)鍵的“動(dòng)態(tài)規(guī)劃”和“貪心”算法,只是把從被人那里看到的內(nèi)容大概理解之后再自己寫(xiě)了一遍而已,代碼中也沒(méi)有相關(guān)的注釋,雖然要寫(xiě)注釋也可以,但是我卻因?yàn)榉N種原因沒(méi)有寫(xiě)(文章后面也有提到),其實(shí)主要原因還是我覺(jué)得這道題花費(fèi)的周期有點(diǎn)長(zhǎng)了,我有點(diǎn)不耐煩了,再加上還有其他事想忙,就導(dǎo)致沒(méi)有詳寫(xiě)的熱情了。所以就想著水一水吧,反正自己回顧的時(shí)候能看得懂就好了。但是沒(méi)想到從前一兩天開(kāi)始,這篇博客居然好像有越來(lái)越多的人閱讀,首先是有2人收藏(看到有人收藏的時(shí)候我自己還疑惑了一下),然后可能是因?yàn)槭詹氐木壒蕦?dǎo)致文章的權(quán)重變高,所以就使得有越來(lái)越多的人看到文章,閱讀量自然也就上升了(是突然上升的那種,剛開(kāi)始看到的時(shí)候還是有點(diǎn)奇怪的),然后就是上面提到的昨晚發(fā)生的事了。然后早上起來(lái)想繼續(xù)寫(xiě)博客,發(fā)現(xiàn)消息通知里有點(diǎn)贊、評(píng)論和私信,我想可能是我之前那篇閱讀量最高的文章有人點(diǎn)贊評(píng)論了吧:
參考:【win10 企業(yè)版 LTSC一鍵安裝微軟應(yīng)用商店Microsoft Store】直接使用GitHub上的開(kāi)源項(xiàng)目,不用自己敲命令(親測(cè)有效!!!),附卸載工具_(dá)賴念安的博客-CSDN博客_win10企業(yè)版ltsc安裝應(yīng)用商店
因?yàn)檫@篇博客確實(shí)是解決了別人的痛點(diǎn)的,會(huì)受到點(diǎn)贊我也不意外。
但是點(diǎn)開(kāi)消息后卻發(fā)現(xiàn)居然是本篇博客的點(diǎn)贊評(píng)論和私信,而且私信是CSDN官方發(fā)的消息,說(shuō)是我的博客上了什么熱榜。
CSDN熱榜官方給我的私信點(diǎn)擊去一看還真有!
我的這篇博客到了熱榜49名后來(lái)一看文章的收藏量也直接從2變成了9,點(diǎn)贊數(shù)倒是保持為3,最奇怪的是,我的粉絲數(shù),突然就從6變?yōu)榱?9(更新這里的時(shí)候好像變?yōu)?1了)。好家伙,兩年的粉絲量都比不上這半天的……
想來(lái)估計(jì)是短時(shí)間內(nèi)有人給我點(diǎn)贊收藏,提高了文章權(quán)重,然后上了熱榜,然后又有人看到,然后繼續(xù)提到權(quán)重……不得不感嘆流量的神奇。
講到這里,我覺(jué)得自己反而沒(méi)有什么特別的興奮之情了,就是覺(jué)得一篇感覺(jué)質(zhì)量不如我之前的博客的文章受到了比往常更多的關(guān)注這件事讓我有點(diǎn)疑惑。同時(shí)也怕到后來(lái)自己寫(xiě)博客的時(shí)候會(huì)考慮太多,反而忘了自己的目的,說(shuō)實(shí)話,我還挺懷念自己五個(gè)小粉絲的時(shí)候的……(當(dāng)然,大家關(guān)注我,我還是報(bào)以感激的~謝謝大家的支持!)希望我的其他自我感覺(jué)質(zhì)量更好的博客也能給大家?guī)?lái)幫助吧。
這篇博客的關(guān)鍵解題思路不是我獨(dú)立想出來(lái)的,所以更多地是在拾人牙慧,其實(shí)更希望大家去原作者那里點(diǎn)贊之類(lèi)的(有關(guān)的參考博客我都在相關(guān)地方寫(xiě)了)。
逼逼賴賴這么久,算是記錄一下自己對(duì)這篇博客的看法吧。總結(jié)下來(lái)就是:
我很疑惑:為啥自己覺(jué)得寫(xiě)得不好的博客卻受到了小關(guān)注,但是自己覺(jué)得寫(xiě)得好的文章反而沒(méi)有呢?不過(guò),無(wú)論如何,如果博客能順便給別人帶來(lái)幫助,那真的很讓我開(kāi)心!
另外,推薦一個(gè)這段時(shí)間看得比較多的LeetCode題解博主的個(gè)人題解網(wǎng)站,感覺(jué)質(zhì)量還是很高的,非常感謝這位博主的分享!
參考:代碼隨想錄
當(dāng)然,LeetCode題解區(qū)也有很多精彩的題解描述,可以對(duì)照思考。
【更新結(jié)束】
總結(jié)
以上是生活随笔為你收集整理的【算法-LeetCode】121. 买卖股票的最佳时机(动态规划;贪心)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 美国纽约大学计算机排名,纽约大学计算机科
- 下一篇: 单片机烧写接口