小白學算法:買賣股票的最佳時機!
本文已收錄至 Github《小白學算法》系列://github.com/vipstone/algorith
今天螞蟻集團(支付寶)正式上市了,毫無疑問這一舉措又造就了一大批富豪,然而作為局外人的我們,也只有羨慕的份了。明明可以考運氣吃飯,咱非得靠實力,你說冤不冤啊?
但話又說回來,能進螞蟻的人也都是牛人,那咱也趕緊提升一下技能吧,好為下一個「螞蟻」做足準備。
今天的這道題比較有意思,是關於「買賣股票」的,題目如下。
題目描述
給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。
如果你最多只允許完成一筆交易(即買入和賣出一支股票一次),設計一個算法來計算你所能獲取的最大利潤。
注意:你不能在買入股票前賣出股票。
示例 1:
輸入: [7,1,5,3,6,4]
輸出: 5
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 5 天(股票價格 = 6)的時候賣出,最大利潤 = 6-1 = 5 。
注意利潤不能是 7-1 = 6, 因為賣出價格需要大於買入價格;同時,你不能在買入前賣出股票。
示例 2:
輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤為 0。
來源:LeetCode
劍指 offer 64://leetcode-cn.com/problems/gu-piao-de-zui-da-li-run-lcof/submissions/
難度:中leetcode 121://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/
難度:簡單
解題思路
根據題目的意思我們知道,我們只有一次交易的機會,也就是買一次再賣一次,但同時要保證收益最大化。那我們本能的直覺是在最低的價格買入,再在最高的價格賣出就好了,如下圖所示:
但這有一個問題,就是我們要保證最高價格要在最低的價格之後,因為我們必須在購買了股票之後才能賣出,而不是相反的順序,這就讓問題變的複雜了。
但此刻我們想到了一個最直接也是最笨的一個方法,那就是用暴力窮舉法,我們使用兩層循環,依次在每個節點買入,然後再在之後的所有節點賣出,這樣來計算節點間的差值(收益),如果此差值大於當前最高收益,就將此值設置為當前最高收益,這樣循環完,我們就能獲得最大收益了。如下圖所示:
方法一:暴力法
有了上面的思路,接下來我們用代碼實現一下:
class Solution {
public int maxProfit(int[] prices) {
int mp = 0; // 最高收益
for (int i = 0; i < prices.length; i++) {
for (int j = i + 1; j < prices.length; j++) {
int item = prices[j] - prices[i];
if (item > mp) mp = item;
}
}
return mp;
}
}
可以看出代碼還是很簡單的,但別高興得太早,我們來看它在 leetcode 上的表現:
複雜度分析
- 時間複雜度:O(n^2),循環運行 n(n-1)/2 次。
- 空間複雜度:O(1),只使用了常數位的變量。
真是一頓操作猛如虎,最終擊敗百分之五!如果用這種代碼去面試的話,估計只能回去等通知了。那有沒有更好的方法呢?答案是肯定的,繼續往下看。
方法二:遍歷一次
對於這道題我們其實可以使用一次循環來實現,先來看下面的這張折線圖:
從上面的圖片我們可以看出,我們在每個節點其實只會做兩件事(第一個節點除外,只能買入不能賣出),這兩件事分別是:買入或賣出。那麼我們其實可以用一個循環來計算出最大的利潤,我們只需要依次對於每個節點做以下兩個判斷:
- 判斷當前節點是不是相對最低價,如果是,則將它設置為最低價(也就是買入);
- 如果當前節點不是最低價,那我們就將它賣出,然後計算賣出的收益(當前節點減去相對最低價),如果賣出的收益大於目前的最高收益,則將此值設置為最高收益。
這樣循環完成之後最高收益就出來了,實現代碼如下:
class Solution {
public int maxProfit(int prices[]) {
if (prices == null || prices.length == 0) return 0;
int mp = 0; // 最高收益
int min = prices[0]; // 最低價
for (int i = 1; i < prices.length; i++) {
if (prices[i] < min) {
// 設定相對最低價
min = prices[i];
} else if (mp < (prices[i] - min)) {
// 設定最高盈利
mp = prices[i] - min;
}
}
return mp;
}
}
以上代碼在 leetcode 中的結果如下圖所示:
複雜度分析
- 時間複雜度:O(n),只需要遍歷一次。
- 空間複雜度:O(1),只使用了常數個變量。
從以上的執行的結果可以看出,這段代碼還算是比較理想的,這樣面試官也會對你豎起大拇指了。
總結
本文我們計算了單次(一次買入和賣出)股票的最高收益,剛開始我們使用的是最簡單粗暴的暴力枚舉法,使用兩層循環依次相減來求出最高收益值,但這種方法的執行效率太低。
然後我們經過觀察折線圖發現,只需要一次循環也能找出最高的收益值。我們只需要在每個節點做兩個判斷,第一:判斷此節點是否為相對最小值,如果是,則記錄下來;如果不是,則計算此值和相對最小值是否為當前最高收益,如果是,則記錄下來。那麼循環一圈之後,我們就能得出最高的收益了,並且執行的效率也很高。
你學會了嗎?有不懂的地方或者更好的方法,歡迎評論區留言~