????????本次分享一道經典的算法題,準確的說是一道題的不同條件下的不同求法。這道題一共有六種情況,每種情況都是不同的解法,在leetcode上對應六道題:
? ? ????前兩題為這個系列的基礎,也是引導之后解法思維方式的關鍵。
第一題,只能交易一次
????????第一題的題目如下:
????????在拿到題目之后,首先需要確定的是:買入股票的時候一定在賣出之前,體現在我們的參數上就是買入的時候的價格在數組中的序號是小于賣出的價格在數組中的序號的。
? ? ? ? 因為確定了上面的這個思路,所以我拿到題目后首先想到的是:我只要知道在每個位置上,在這個位置之前最小的數和這個位置之后最大的數,就可以通過差值計算拿到在該位置左右進行買入賣出能拿到的最大利潤。
? ? ? ? 首先從實現上來說,這樣子解這道題肯定是沒有問題的,只需要最多進行三次循環:正循環一次,記錄到每個位置時能買入的最低值;反著循環一次,記錄到每個位置時,在這個位置之后會出現的最大賣出值;再循環一次,計算每個位置上在左邊買右邊賣能賺到的最大值。簡單優化后,可以將三次循環減少為兩次:正向掃描一次找到所有位置左邊的最小值。反著掃描的時候,在拿到該位置右邊的最大值的時候,直接與左邊的最小值求差。但即便這樣,在此題上消耗的時間最少為2N,空間最少為N(N為數組的長度,空間消耗最小為一個用來存儲到每個位置時左邊最小值的數組)。
????????其實這道題不論從時間還是空間上,都還能有很大的優化空間:
? ? ? ? 上面我們已經知道了,買入一定發生在賣出之前,即買入的價格在數組中的序號一定是小于賣出的價格在數組中的序號的。同時,我們希望買入的價盡可能的小,而賣出的價盡可能的高。
? ? ? ? 由“買入一定在賣出左邊”這里想到,是不是只進行一次循環就可以了。所以可以從數組的第一個數開始向后思考:
????????我們用兩個變量來記錄兩個值,一個值是我們到目前為止碰到的最小的數(到目前為止可以買入的最低價),另一個變量用來記錄我們到目前為止已經完成買入賣出的話可以賺到的最大值。從第一個數開始向最后循環,每碰到一個數,如果這個數比我們存的最小買入價更小,我們更新最小買入價,這個值可以在之后賣出的時候提供一個更小的買入價。如果當前碰到的價格比現在存的最小買入價要大,我們就計算如果現在賣出可以賺到的數值,然后與我們記錄的在之前的最大差值,如果更大的話就更新。這樣在掃描了一遍之后,我們記錄差值的變量就必然會被更新成為最小買入與最大賣出的差值。python代碼實現如下:
第二題,不限制交易次數
? ? ? ? 第二題與第一題的區別在于,題目的限制由只能買賣一次變成了可以進行無數次的買賣,但是同時只能有一筆交易在進行中,即只有將之前買的賣出之后,才能進行下一次的買入。
? ? ? ? 既然可以進行無數次的交易,那么我們需要考慮的就是“用最少的時間和空間將所有的利潤都獲取到”。
? ? ? ? 因為在第一題中我們已經實現了只掃描一次的算法,所以現在我們仍然在只掃描一次的基礎上思考如何拿到所有可以賺到的差值。
? ? ? ? 當我們掃描到位置 i 的時候,如果price[i]是大于price[i - 1]的,那么這個差值就可以作為我們賺到的。因為我們不需要考慮交易次數的限制,所以我們一定可以通過通過交易拿到這個差值。如果price[i]小于price[i - 1]那么我們認為在之前的交易已經結束了,我們更新我們的買入價,之后遇到較高的價的時候,與當前的低買入價求差。我們的宗旨是:只要在漲,就賺下差值,只要跌了,就重新開始。因為我們碰到下面這種情況的時候,x1+x2一定是大于x3的,所以一旦我們在掃描的時候發現當前值就變小了,就開始重新算差值。
? ? ? ? 具體python實現如下:
第三題,最多交易兩次
? ? ? ? 第三題的限制是最多進行兩次交易,同一時間只能有一筆交易存在。
? ? ? ? 因為兩筆交易相對獨立,因為我們有第一題的基礎,所以我們可以將這題進行拆解:找到一個位置,在這個位置左邊完成一次交易,右邊完成一次交易,得到兩次交易的差值之和。
? ? ? ? 在第一題中,我們從提一個數開始向后掃描,掃到哪個位置就能得到一直到那個位置我們完成一次交易所能得到的最大差值。所以在此題中可以進行正反兩次掃描,分別獲得每個位置前面完成一次交易所能得到的最大差值和后面完成一次交易能得到的最大值。然后計算所有位置上兩次交易得到的結果,取到最大值,就是此題的答案。
擴展
? ? ? ? 通過以上三題,我們可以發現題目中真正限定的其實就是交易的次數。雖然三道題的實現過程都不一樣,但是我們是可以將三道題進行一個共同的抽象的:
? ? ? ? 給定一個數組表示股票每天的價格,在最多完成 k 次交易的情況下如何獲利最多。
? ? ? ? 此時,我們就需要換一種思路,找到一個更通用的方法。因為現在k對于我們來說是一個變量,k表示的是最多能完成的交易次數,而不是必須要完成的交易次數。所以交易1~k次的情況我們都需要考慮。
? ??????我們定義local[i][j]為在到達第i天時最多可進行j次交易并且最后一次交易在最后一天賣出的最大利潤,此為局部最優。然后我們定義global[i][j]為在到達第i天時最多可進行j次交易的最大利潤,此為全局最優。它們的遞推式為:
? ? diff = prices[i] - prices[i - 1]
????local[i][j] = max(global[i - 1][j - 1] + max(diff, 0), local[i - 1][j] + diff)
????global[i][j] = max(local[i][j], global[i - 1][j])
? ? ? ? 我們需要進行兩級的循環,分別對應 i 與 j 。這樣在完成循環后,global[len(prices)][k]就是我們所要求的值。也即股票買賣的第四題。
? ? ? ? 本次只分享到這里,股票買賣的最后兩題又加入了新的額外限定條件,如果有興趣可以嘗試解答一下。