本文從屬於筆者的數據結構與算法系列文章中的動態規劃部分,同時也概括於筆者的個人校招準備之路:從Web前端到服務端應用架構這篇綜述。前端
Leetcode上有五道關於股票買賣相關的問題,恰巧筆者面試阿里的時候也被問及,所以在本文中略做總結,這五個問題列舉以下:java
本題意思就是你獲得一系列在接下來幾天的股票價格,如今你被容許只用一次交易(就是買進再賣出)來獲取最大利益。 這個很簡單,只要用雙指針的方法記住獲利的大小,再篩選出最大的便可。代碼以下:git
/** * @function 僅容許買賣一次 * @description Say you have an array for which the ith element is the price of a given stock on day i.If you were only permitted to complete at most one transaction (ie, buy one and sell one share of the stock), design an algorithm to find the maximum profit. * @OJ https://leetcode.com/problems/best-time-to-buy-and-sell-stock/ */ public class Stock1 { public int maxProfit(int[] prices) { //存儲最大值的變量,初始化爲0 int ans = 0; //判斷是否有效價格序列 if (prices.length == 0) { return ans; } //判斷應該在哪一日買入,即最小值 int bought = prices[0]; //遍歷全部交易日價格 for (int i = 1; i < prices.length; i++) { //判斷本日是否可以賣出 if (prices[i] > bought) { //判斷若是本日賣出收益是否最大 if (ans < (prices[i] - bought)) { ans = prices[i] - bought; } } else { //判斷本日是否爲最低價格 bought = prices[i]; } } return ans; } }
意思是買賣股票時能夠不計買賣次數,可是必須在買以前先把之前的股票賣掉。而後求能獲利最大的額度。 這樣的話也很簡單,只要碰見下一天的價格比這一天價格高的話,就賣出。代碼以下:github
package wx.algorithm.op.dp.stock; /** * Created by apple on 16/8/21. */ /** * @function 可以進行無窮屢次買賣, 求取最大值 * @OJ https://github.com/wxyyxc1992/just-coder-handbook/blob/master/Algorithm/java/src/main/java/wx/algorithm/op/dp/stock/Stock2.java */ public class Stock2 { public int maxProfit(int[] prices) { int total = 0; //遍歷全部交易日 for (int i = 0; i < prices.length - 1; i++) { //只要是後一天比前一天貴,就賣出 if (prices[i + 1] > prices[i]) total += prices[i + 1] - prices[i]; } return total; } }
如今你被容許買賣的次數減小到最多交易兩次,也是必須先賣出再買進。而後求能獲利的最大額度。 面試
咱們來琢磨一下這個規則,每次交易必須先賣掉手上這隻股票才能進行下次操做,好比下面幾天的股票價格爲【1,7,15,6,57,32,76】,買第一隻股票價格爲¥1,爲了最大獲利,在¥76時賣掉確定最好,因爲交易時必須先賣再買,因此要是這麼交易的話,只能交易一次。可是如今咱們有兩次買賣機會,也許在這幾天中,中間兩次的交易就的獲利總和就能超過¥76-¥1=¥75,因此咱們要來找到這個組合。既然交易不能交叉(must sell the stock before you buy again),也就是說咱們在遍歷價格差找最大獲益時,能夠首位同時進行。能夠用雙向動態規劃的思想來作。代碼以下算法
package wx.algorithm.op.dp.stock; /** * Created by apple on 16/8/21. */ /** * @function 最多兩次買賣 * @OJ https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/ */ public class Stock3 { public static int maxProfit(int[] prices) { //判斷是否爲有效交易天數 if (prices.length == 0) return 0; //存放左半部分最大收益 int[] left = new int[prices.length]; //存放右半部分最大收益 int[] right = new int[prices.length]; //初始化爲0 int leftMin = prices[0]; int rightMax = prices[prices.length - 1]; //總收益 int sum = 0; //計算左半段最大收益 for (int i = 1; i < prices.length; i++) { //獲取左半部分的最低價 leftMin = Math.min(prices[i], leftMin); //獲取左半部分最大收益 left[i] = Math.max(prices[i] - leftMin, left[i - 1]); } //計算右半段最大收益 for (int i = prices.length - 2; i >= 0; i--) { //獲取右半部分最低價 rightMax = Math.max(prices[i], rightMax); //獲取右半部分最大收益 right[i] = Math.max(rightMax - prices[i], right[i + 1]); } //找出兩次交易最大收益組合 for (int i = 0; i < prices.length; i++) { if ((left[i] + right[i]) > sum) sum = left[i] + right[i]; } return sum; } public static void main(String args[]) { int[] prices = new int[]{1, 7, 15, 6, 57, 32, 76}; System.out.print("maxProfit is" + maxProfit(prices)); } }
在上面left和right的計算當中,left存儲的獲利爲【¥0,¥6,¥14,¥14,¥56,¥56,¥75】,right存儲的獲利爲【¥75,¥70,¥70,¥70,¥44,¥44,¥0】,能夠看出買入第一次股票,在最後一天賣出股票獲利是作多的是¥75,可是因爲能夠交易兩次,咱們能夠看出在¥1時買入,¥57時賣出,再在¥32時買入,在¥76賣出,獲利是最多的,一共是¥100.數組
此狀況下容許用戶買賣屢次,以期獲取最大值。最方便想到的辦法會是回溯,不過不可避免的會致使重複計算,所以最好的仍是進行動態規劃。當咱們考慮如何構建動態規劃的狀態轉換函數時,首先假想下咱們構建的狀態轉移表,應該會包含k,即咱們的交易次數與i,即交易天數這兩個變量。不過本題中存在的問題是每一個i同時還存在三個狀態,即i天買入或者賣出或者啥都不作,抽象成兩個狀態的就是第i天是持有股票仍是不持有股票。咱們能夠爲此創建兩個向量:數據結構
hold[i][j]
:對於0~i天中最多進行j次交易而且第i天仍然持有股票的收益架構
unhold[i][j]
:對於0~i天中最多進行j次交易而且第i天不持有股票的收益app
第i天持有股票的收益爲 以前買了股票但尚未賣出 或者 今天才選擇買入股票 兩者中較大值
$$
holdi = Math.max(unholdi-1-prices[i],holdi-1)
$$
第i天不持有股票的收益爲 選擇今天賣出 或者 今天不買入時 最大的收益
$$ unholdi = Math.max(holdi-1+prices[i],unholdi-1) $$
代碼以下
package wx.algorithm.op.dp.stock; /** * Created by apple on 16/8/21. */ public class Stock4 { /** * @param k 可交易的次數 * @param prices 價格向量 * @return * @function 計算最多K次狀況下能得到的最大理論 */ public int maxProfit(int k, int[] prices) { if (k == 0 || prices.length < 2) return 0; if (k > prices.length / 2) return noLimit(prices); // hold[i][j]: 對於0~i天中最多進行j次交易而且第i天仍然持有股票的收益 // unhold[i][j]: 對於0~i天中最多進行j次交易而且第i天不持有股票的收益 // 第i天持有股票的收益爲 以前買了股票但尚未賣出 或者 今天才選擇買入股票 兩者中較大值 // hold[i][j] = Math.max(unhold[i-1][j]-prices[i],hold[i-1][j]); // 第i天不持有股票的收益爲 選擇今天賣出 或者 今天不買入時 最大的收益 // unhold[i][j] = Math.max(hold[i-1][j-1]+prices[i],unhold[i-1][j]); int[][] hold = new int[k + 1][prices.length]; int[][] unhold = new int[k + 1][prices.length]; for (int i = 1; i <= k; i++) { //初始化持有狀態下的初始值 hold[i][0] = -prices[0]; //初始化不持有狀態下的初始值 爲0 unhold[i][0] = 0; for (int j = 1; j < prices.length; j++) { hold[i][j] = Math.max(-prices[j] + unhold[i - 1][j], hold[i][j - 1]); // Buy or not buy unhold[i][j] = Math.max(prices[j] + hold[i][j - 1], unhold[i][j - 1]); // Sell or not sell } } //真實狀況下最後一天了仍是要賣出的 return unhold[k][prices.length - 1]; } private int noLimit(int[] prices) { // Solution from Best Time to Buy and Sell Stock II int max = 0; for (int i = 0; i < prices.length - 1; i++) { if (prices[i + 1] > prices[i]) max += prices[i + 1] - prices[i]; } return max; } }
本題意爲用戶在賣出以後須要休息一天才能進行操做,那麼在本題中用戶是存在有三個狀態,未持有(unhold)、持有(hold)、休息(cooldown)這三種,這三種狀態的狀態轉化圖爲:
其對應的狀態轉換函數爲:
unhold[i] = max(unhold[i - 1], cooldown[i - 1]); // Stay at s0, or rest from s2 hold[i] = max(hold[i - 1], unhold[i - 1] - prices[i]); // Stay at s1, or buy from s0 cooldown[i] = hol[i - 1] + prices[i]; // Only one way from s1
代碼爲:
package wx.algorithm.op.dp.stock; /** * Created by apple on 16/8/21. */ public class StockWithCoolDown { public int maxProfit(int[] prices) { //判斷日期長度是否大於1 if (prices.length <= 1) { return 0; } //構建三個狀態數組 int[] unhold = new int[prices.length]; int[] hold = new int[prices.length]; int[] cooldown = new int[prices.length]; unhold[0] = 0; hold[0] = -prices[0]; cooldown[0] = Integer.MIN_VALUE; for (int i = 1; i < prices.length; i++) { unhold[i] = Math.max(unhold[i - 1], cooldown[i - 1]); hold[i] = Math.max(hold[i - 1], unhold[i - 1] - prices[i]); cooldown[i] = hold[i - 1] + prices[i]; } return Math.max(unhold[prices.length - 1],cooldown[prices.length - 1]); } }