買賣股票的最佳時機
假設有一個數組,它的第i個元素是一支給定的股票在第i天的價格。如果你最多只允許完成一次交易(例如,一次買賣股票),設計一個算法來找出最大利潤。
給出一個數組樣例 [3,2,3,1,2], 返回 1
解題
法一:直接暴力,時間發雜度O(N2)
public class Solution { /** * @param prices: Given an integer array * @return: Maximum profit */ public int maxProfit(int[] prices) { // write your code here int Max = 0; if( prices == null || prices.length == 0) return 0; for(int i = 0;i< prices.length ;i++){ for(int j = i;j< prices.length ;j++) Max = Math.max(Max, prices[j] - prices[i]); } return Max; } }
法二:動態規划,選取最小的賣,最大的買,利潤最大。
public class Solution { /** * @param prices: Given an integer array * @return: Maximum profit */ public int maxProfit(int[] prices) { // write your code here int result = 0; if( prices == null || prices.length == 0) return 0; int minbuy = prices[0]; for(int i = 1;i< prices.length ;i++){ // 最小的購買,最大的賣 result = Math.max(result,prices[i] - minbuy); minbuy = Math.min(minbuy,prices[i]); } return result; } }
時間復雜度O(N)
Python
class Solution: """ @param prices: Given an integer array @return: Maximum profit """ def maxProfit(self, prices): # write your code here if prices == None or len(prices) ==0: return 0 Min = prices[0] res = 0 for p in prices: res = max(res,p-Min) Min = min(Min,p) return res
法三:分治法
參考《算法導論》

題目要求的是一次購買,一次賣出使得所獲價格變化最大。可以考慮每一天的價格變化,第i天的價格變化 等於第i天的價格減去i-1天的價格,這樣就會有許多價格變化的數據形成的數組,求這個數組的連續子數組的和的最大值就是答案了。為什么這個這個最大連續子數組就是答案?
假設原始數組是:
,若最大收益是 an - a1
相鄰差數組是:
,顯然這個的連續和也是an - a1
問題轉化為求最大連續子數組
對上圖的例子:

分治法求解最大子數組
假定我們要尋找子數組A[low,...,high]的最大子數組。使用分治法意味着我們要將子數組分成兩個規模盡量相同的子數組。也就是說,找到子數組的中央位置,比如:mid,然后考慮求兩個子數組A[low,...,mid] 和A[mid+1,...,high]。
A[low,...,high]的然后連續子數組A[i,...,j]所處的位置必然是一下三種情況之一:
(1)完全位於子數組A[low,...,mid]中,因此low<=i<=j<=mid
(2)完全位於子數組A[mid+1,...,high]中,因此mid+1<=i<=j<=high
(3)跨越了中間點,因此low<=i<=mid<=j<=high
所以,可以遞歸的求解(1)(2)兩種情況的最大子數組,剩下的就是對(3)情況尋找跨越中間點的最大子數組,然后在三種情況中選取和最大者。如下圖所示

對於跨越中間點的最大子數組,可以在線性時間內求解。可以找出A[i,...,mid] 和A[mid+1,...,j]的最大子數組,合並就是答案。
參考算法導論寫的尋找經過中間點時候的最大連續子數組
public int findMaxCrossingSubarray(int[] A,int low,int mid,int high){ if(low > mid || mid>high) return Integer.MIN_VALUE; int leftSum = Integer.MIN_VALUE; int rightSum = Integer.MIN_VALUE; int sum = 0; int maxleft = -1; int maxright = -1; for(int i = mid;i>=low;i--){ sum+=A[i]; if( sum >= leftSum){// 向左只要和增加就更新 leftSum = sum; maxleft = i; } } sum = 0; for(int j = mid+1;j<=high;j++){ sum+=A[j]; if(sum>=rightSum){ rightSum = sum; maxright = j; } } return leftSum + rightSum; }
算法導論上的偽代碼

時間復雜度O(N)
上面有返回的邊界,我只是返回了子數組的最大值
下面在遞歸的求解整個數組的最大連續子數組
public int findMaxSubarray(int[] A,int low,int high){ if(low == high) return Math.max(A[low],0); else{ int mid = low + (high - low)/2;// 防止越界 int leftSum = findMaxSubarray(A,low,mid);//(1) int rightSum = findMaxSubarray(A,mid+1,high);//(2) int midSum = findMaxCrossingSubarray(A,low,mid,high);//(3) int sum = Math.max(leftSum,rightSum); sum = Math.max(sum,midSum); sum = Math.max(sum,0); return sum; } }
上面標的(1)( 2)( 3)對應上面分析的(1)(2)(3)
上面代碼中最后的結果和0求了最大值,lintcode測試用例可以不買不賣的情況,由於買了一定會虧,就不買了的情況,題目要求最大一次交易,就是可以不交易的了。
算法導論上的偽代碼

時間復雜度分析:
遞歸情況:
這個等式很顯然的
當n=1的時候就是O(1)
所以:

時間復雜度是:
具體時間復雜度求解參考《算法導論》
對於求解最大子數組,當然也可以運用動態規划求解
全部程序
public class Solution { /** * @param prices: Given an integer array * @return: Maximum profit */ public int maxProfit(int[] prices) { // write your code here if(prices == null || prices.length == 0) return 0; int[] A = new int[prices.length - 1]; for(int i = 1;i<prices.length ;i++) A[i-1] = prices[i] - prices[i-1]; int maxSubarray = findMaxSubarray(A,0,A.length - 1); return maxSubarray; } public int findMaxSubarray(int[] A,int low,int high){ if(low == high) return Math.max(A[low],0); else{ int mid = low + (high - low)/2;// 防止越界 int leftSum = findMaxSubarray(A,low,mid);//(1) int rightSum = findMaxSubarray(A,mid+1,high);//(2) int midSum = findMaxCrossingSubarray(A,low,mid,high);//(3) int sum = Math.max(leftSum,rightSum); sum = Math.max(sum,midSum); sum = Math.max(sum,0); return sum; } } public int findMaxCrossingSubarray(int[] A,int low,int mid,int high){ if(low > mid || mid>high) return Integer.MIN_VALUE; int leftSum = Integer.MIN_VALUE; int rightSum = Integer.MIN_VALUE; int sum = 0; int maxleft = -1; int maxright = -1; for(int i = mid;i>=low;i--){ sum+=A[i]; if( sum >= leftSum){// 向左只要和增加就更新 leftSum = sum; maxleft = i; } } sum = 0; for(int j = mid+1;j<=high;j++){ sum+=A[j]; if(sum>=rightSum){ rightSum = sum; maxright = j; } } return leftSum + rightSum; } }
