小白學算法:買賣股票的最佳時機!

本文已收錄至 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/
難度:簡單

解題思路

根據題目的意思我們知道,我們只有一次交易的機會,也就是買一次再賣一次,但同時要保證收益最大化。那我們本能的直覺是在最低的價格買入,再在最高的價格賣出就好了,如下圖所示:

image.png

但這有一個問題,就是我們要保證最高價格要在最低的價格之後,因為我們必須在購買了股票之後才能賣出,而不是相反的順序,這就讓問題變的複雜了。

但此刻我們想到了一個最直接也是最笨的一個方法,那就是用暴力窮舉法,我們使用兩層循環,依次在每個節點買入,然後再在之後的所有節點賣出,這樣來計算節點間的差值(收益),如果此差值大於當前最高收益,就將此值設置為當前最高收益,這樣循環完,我們就能獲得最大收益了。如下圖所示:

image.png

方法一:暴力法

有了上面的思路,接下來我們用代碼實現一下:

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 上的表現:
image.png

複雜度分析

  • 時間複雜度:O(n^2),循環運行 n(n-1)/2 次。
  • 空間複雜度:O(1),只使用了常數位的變量。

真是一頓操作猛如虎,最終擊敗百分之五!如果用這種代碼去面試的話,估計只能回去等通知了。那有沒有更好的方法呢?答案是肯定的,繼續往下看。

方法二:遍歷一次

對於這道題我們其實可以使用一次循環來實現,先來看下面的這張折線圖:

image.png

從上面的圖片我們可以看出,我們在每個節點其實只會做兩件事(第一個節點除外,只能買入不能賣出),這兩件事分別是:買入或賣出。那麼我們其實可以用一個循環來計算出最大的利潤,我們只需要依次對於每個節點做以下兩個判斷:

  • 判斷當前節點是不是相對最低價,如果是,則將它設置為最低價(也就是買入);
  • 如果當前節點不是最低價,那我們就將它賣出,然後計算賣出的收益(當前節點減去相對最低價),如果賣出的收益大於目前的最高收益,則將此值設置為最高收益。

這樣循環完成之後最高收益就出來了,實現代碼如下:

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 中的結果如下圖所示:
image.png

複雜度分析

  • 時間複雜度:O(n),只需要遍歷一次。
  • 空間複雜度:O(1),只使用了常數個變量。

從以上的執行的結果可以看出,這段代碼還算是比較理想的,這樣面試官也會對你豎起大拇指了。

總結

本文我們計算了單次(一次買入和賣出)股票的最高收益,剛開始我們使用的是最簡單粗暴的暴力枚舉法,使用兩層循環依次相減來求出最高收益值,但這種方法的執行效率太低。

然後我們經過觀察折線圖發現,只需要一次循環也能找出最高的收益值。我們只需要在每個節點做兩個判斷,第一:判斷此節點是否為相對最小值,如果是,則記錄下來;如果不是,則計算此值和相對最小值是否為當前最高收益,如果是,則記錄下來。那麼循環一圈之後,我們就能得出最高的收益了,並且執行的效率也很高。

你學會了嗎?有不懂的地方或者更好的方法,歡迎評論區留言~

Tags: