小白學(xué)算法:買賣股票的最佳時(shí)機(jī)!

作者 | 王磊
來源 | Java中文社群(ID:javacn666)
轉(zhuǎn)載請(qǐng)聯(lián)系授權(quán)(微信ID:GG_Stone)本文已收錄至 Github《小白學(xué)算法》系列:https://github.com/vipstone/algorith
今天螞蟻集團(tuán)(支付寶)正式上市了,毫無疑問這一舉措又造就了一大批富豪,然而作為局外人的我們,也只有羨慕的份了。明明可以靠運(yùn)氣吃飯,咱非得靠實(shí)力,你說虧不虧啊?
但話又說回來,能進(jìn)螞蟻的人也都是牛人,那咱也趕緊提升一下技能吧,好為下一個(gè)“螞蟻”做足準(zhǔn)備。

今天的這道題比較有意思,是關(guān)于「買賣股票」的,題目如下。
題目描述
給定一個(gè)數(shù)組,它的第 i 個(gè)元素是一支給定股票第 i 天的價(jià)格。如果你最多只允許完成一筆交易(即買入和賣出一支股票一次),設(shè)計(jì)一個(gè)算法來計(jì)算你所能獲取的最大利潤。
注意:你不能在買入股票前賣出股票。
示例 1:
輸入: [7,1,5,3,6,4]
輸出: 5
解釋: 在第 2 天(股票價(jià)格 = 1)的時(shí)候買入,在第 5 天(股票價(jià)格 = 6)的時(shí)候賣出,最大利潤 = 6-1 = 5 。? ? 注意利潤不能是 7-1 = 6, 因?yàn)橘u出價(jià)格需要大于買入價(jià)格;同時(shí),你不能在買入前賣出股票。
示例 2:
輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤為 0。
來源:LeetCode
劍指 offer 64:https://leetcode-cn.com/problems/gu-piao-de-zui-da-li-run-lcof/submissions/難度:中
leetcode 121:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/難度:簡(jiǎn)單
解題思路
根據(jù)題目的意思我們知道,我們只有一次交易的機(jī)會(huì),也就是買一次再賣一次,但同時(shí)要保證收益最大化。那我們本能的直覺是在最低的價(jià)格買入,再在最高的價(jià)格賣出就好了,如下圖所示:

但這有一個(gè)問題,就是我們要保證最高價(jià)格要在最低的價(jià)格之后,因?yàn)槲覀儽仨氃谫徺I了股票之后才能賣出,而不是相反的順序,這就讓問題變的復(fù)雜了。
但此刻我們想到了一個(gè)最直接也是最笨的一個(gè)方法,那就是用暴力窮舉法,我們使用兩層循環(huán),依次在每個(gè)節(jié)點(diǎn)買入,然后再在之后的所有節(jié)點(diǎn)賣出,這樣來計(jì)算節(jié)點(diǎn)間的差值(收益),如果此差值大于當(dāng)前最高收益,就將此值設(shè)置為當(dāng)前最高收益,這樣循環(huán)完,我們就能獲得最大收益了。如下圖所示:

方法一:暴力法
有了上面的思路,接下來我們用代碼實(shí)現(xiàn)一下:
class?Solution?{
????public?int?maxProfit(int[]?prices)?{
????????int?mp?=?0;?//?最高收益
????????for?(int?i?=?0;?i?????????????for?(int?j?=?i?+?1;?j?????????????????int?item?=?prices[j]?-?prices[i];
????????????????if?(item?>?mp)?mp?=?item;
????????????}
????????}
????????return?mp;
????}
}
可以看出代碼還是很簡(jiǎn)單的,但別高興得太早,我們來看它在 leetcode 上的表現(xiàn):

復(fù)雜度分析
- 時(shí)間復(fù)雜度:O(n^2),循環(huán)運(yùn)行 n(n-1)/2 次。
- 空間復(fù)雜度:O(1),只使用了常數(shù)位的變量。
真是一頓操作猛如虎,最終擊敗百分之五!如果用這種代碼去面試的話,估計(jì)只能回去等通知了。那有沒有更好的方法呢?答案是肯定的,繼續(xù)往下看。
方法二:遍歷一次
對(duì)于這道題我們其實(shí)可以使用一次循環(huán)來實(shí)現(xiàn),先來看下面的這張折線圖:

從上面的圖片我們可以看出,我們在每個(gè)節(jié)點(diǎn)其實(shí)只會(huì)做兩件事(第一個(gè)節(jié)點(diǎn)除外,只能買入不能賣出),這兩件事分別是:買入或賣出。那么我們其實(shí)可以用一個(gè)循環(huán)來計(jì)算出最大的利潤,我們只需要依次對(duì)于每個(gè)節(jié)點(diǎn)做以下兩個(gè)判斷:
- 判斷當(dāng)前節(jié)點(diǎn)是不是相對(duì)最低價(jià),如果是,則將它設(shè)置為最低價(jià)(也就是買入);
- 如果當(dāng)前節(jié)點(diǎn)不是最低價(jià),那我們就將它賣出,然后計(jì)算賣出的收益(當(dāng)前節(jié)點(diǎn)減去相對(duì)最低價(jià)),如果賣出的收益大于目前的最高收益,則將此值設(shè)置為最高收益。
這樣循環(huán)完成之后最高收益就出來了,實(shí)現(xiàn)代碼如下:
class?Solution?{
????public?int?maxProfit(int?prices[])?{
????????if?(prices?==?null?||?prices.length?==?0)?return?0;
????????int?mp?=?0;?//?最高收益
????????int?min?=?prices[0];?//?最低價(jià)
????????for?(int?i?=?1;?i?????????????if?(prices[i]?????????????????//?設(shè)定相對(duì)最低價(jià)
????????????????min?=?prices[i];
????????????}?else?if?(mp?(prices[i]?-?min))?{
????????????????//?設(shè)定最高盈利
????????????????mp?=?prices[i]?-?min;
????????????}
????????}
????????return?mp;
????}
}
以上代碼在 leetcode 中的結(jié)果如下圖所示:

復(fù)雜度分析
- 時(shí)間復(fù)雜度:O(n),只需要遍歷一次。
- 空間復(fù)雜度:O(1),只使用了常數(shù)個(gè)變量。
從以上的執(zhí)行的結(jié)果可以看出,這段代碼還算是比較理想的,這樣面試官也會(huì)對(duì)你豎起大拇指了。
總結(jié)
本文我們計(jì)算了單次(一次買入和賣出)股票的最高收益,剛開始我們使用的是最簡(jiǎn)單粗暴的暴力枚舉法,使用兩層循環(huán)依次相減來求出最高收益值,但這種方法的執(zhí)行效率太低。
然后我們經(jīng)過觀察折線圖發(fā)現(xiàn),只需要一次循環(huán)也能找出最高的收益值。我們只需要在每個(gè)節(jié)點(diǎn)做兩個(gè)判斷,第一:判斷此節(jié)點(diǎn)是否為相對(duì)最小值,如果是,則記錄下來;如果不是,則計(jì)算此值和相對(duì)最小值是否為當(dāng)前最高收益,如果是,則記錄下來。那么循環(huán)一圈之后,我們就能得出最高的收益了,并且執(zhí)行的效率也很高。
你學(xué)會(huì)了嗎?有不懂的地方或者更好的方法,歡迎評(píng)論區(qū)留言~
—?【 THE END 】—本公眾號(hào)全部博文已整理成一個(gè)目錄,請(qǐng)?jiān)诠娞?hào)里回復(fù)「m」獲取!
3T技術(shù)資源大放送!包括但不限于:Java、C/C++,Linux,Python,大數(shù)據(jù),人工智能等等。在公眾號(hào)內(nèi)回復(fù)「1024」,即可免費(fèi)獲取!!
