1. 程式人生 > >[LeetCode ] Best Time to Buy and Sell Stock系列題解

[LeetCode ] Best Time to Buy and Sell Stock系列題解

這一系列的題目的基本模型是給出n天每天股票的價格,可以在任意一天買股票,在之後的任意一天把股票賣出,賺取差價,限制是手中最多有一張股票,必選把手中的股票賣掉才能再買股票。求最大獲利。

這是最簡單的一道題,在基本模型的基礎上添加了一個限制條件,最多交易一次。

我們從第一天開始遍歷,維護一個已經遍歷過的股票的最小价格minPrice,答案為max(prices[ i ] - minPrice)。

Java程式碼:

class Solution {
     public int maxProfit(int[] prices) {
		 if(prices.length == 0) return 0;
		 int res,minPrice;
		 res = 0;
		 minPrice = prices[0];
		 for(int i = 1; i < prices.length; i++) {
			 res = Math.max(res, prices[i] - minPrice);
			 minPrice = Math.min(minPrice, prices[i]);
		 }
		 return res;
	 }
}

在基本模型的基礎上,交易的次數不受限制。

既然交易的次數不受限制,我們可以貪心的來思考了,只要第i天股票的價格大於第i-1天的股票價格,我們就進行交易,這樣最後得到的結果肯定是最優的。

Java程式碼:

class Solution {
   public int maxProfit(int[] prices) {
		 if(prices.length == 0) return 0;
		 int res;
		 res = 0;
		 for(int i = 1; i < prices.length; i++) {
			 if(prices[i] > prices[i - 1]) {
				 res += prices[i] - prices[i - 1];
			 }
		 }
		 return res;
	 }
}

在基本模型的基礎上,最多交易2次。

兩次交易肯定不會交叉,即前i天發生第一次交易,第二次交易肯定會在第i天到第n天中發生。用t1[ i ]表示第0天到第i天最多交易一次的最大獲利,t2[ i ]表示從第i天到第n-1天的最多交易一次的最大獲利,則答案可表示為max(t1[ i ] + t2[ i ] ),至於t1[ i ],t2[ i ]如何得到可以參考題目1的方法。

Java程式碼:

class Solution {
    public int maxProfit(int[] prices) {
        if(prices.length == 0) return 0;
		int res,minPrice,maxPrice,MaxProfit;
		int[] t1 = new int[prices.length];
		int[] t2 = new int[prices.length];
		t1[0] = MaxProfit = 0;
		minPrice = prices[0];
		for(int i = 1; i < prices.length; i++) {
			MaxProfit = Math.max(MaxProfit, prices[i] - minPrice);
			minPrice = Math.min(minPrice, prices[i]);
			t1[i] = MaxProfit;
		}
		t2[prices.length - 1] = MaxProfit = 0;
		maxPrice = prices[prices.length - 1];
		for(int i = prices.length - 1; i >= 0; i--) {
			MaxProfit = Math.max(MaxProfit, maxPrice - prices[i]);
			maxPrice = Math.max(maxPrice, prices[i]);
			t2[i] = MaxProfit;
		}
		res = 0;
		for(int i = 0; i < prices.length; i++) {
			res = Math.max(res, t1[i] + t2[i]);
		}
		return res;
	}
}

這道題實在基本模型的基礎上,最多交易k次。

首先我們考慮當k無限大時,就和題目2一樣了,我們可以先用題目二的方法解出來需要交易的次數num,如果k>=num,直接返回按照題目二得到的結果。如果k<num,就需要我們向動態規劃上考慮了。

按照常規的dp思路dp[ i ][ j ]表示在第i天交易j次的最大收益 ,轉移方程為:

dp[i][j]=max\ (dp[i-1][j-1]+diff , \right dp[i-1][j] )

diff= prices[i]-prices[i-1]

但這樣其實是不對的,如果在第i天和第i-1天同時有交易發生,這兩次交易可以合併為1次交易,而上述方法則把它們算為兩次交易,所以我們得到的最大收益就會變小了。

正確思路是用一個區域性最優解local[ i ][ j ]和一個全域性最優解global[ i ] [ j ]。

local[ i ][ j ]表示前i天進行j次交易,並且第j次交易發生在第i天的最大收益。

global[ i ] [ j ]表示前i天進行j次交易,但第j次交易並不一定發生在第i天的最大收益。轉移方程為:

local[i][j]= max\left ( globla[i-1][j-1]+max(diff,0) , \right local[i-1][j] + diff )

global[i][j]=max(globla[i - 1][j],local[i][j])

其中區域性最優值是比較前一天並少交易一次的全域性最優加上大於0的差值,和前一天的區域性最優加上差值後相比,注意前一天的區域性交易了j次,加上差值還是為j次,這就避免了第一種方法的那種錯誤出現,並且diff無論正負都得加上(想想local的表示含義就理解了),兩者之中取較大值,而全域性最優比較區域性最優和前一天的全域性最優。

Java程式碼,當然可以利用01揹包那種思想節省一下記憶體。

class Solution {
    public int maxProfit(int k, int[] prices) {
        if(prices.length == 0) return 0;
		int res,num;
		res = num = 0;
		for(int i = 1; i < prices.length; i++) {
			if(prices[i] > prices[i - 1]) {
				res += prices[i] - prices[i - 1];
				num++;
			}
		}
		if(num <= k) return res;
		int[][] global = new int[prices.length][k + 1];
		int[][] local = new int[prices.length][k + 1];
		for(int i = 1; i < prices.length; i++) {
			for(int j = 1; j <= k; j++) {
				int diff = prices[i] - prices[i - 1];
				local[i][j] = Math.max(global[i - 1][j - 1] + Math.max(diff, 0),local[i - 1][j] + diff);
				global[i][j] = Math.max(local[i][j],global[i - 1][j]);
			}
		}
		return global[prices.length - 1][k];
    }
}

在基本模型的基礎上,添加了在第i天賣掉股票後的一天不能再買的限制。

整個過程中有三種狀態:

  • s0:未持有有股票,可以購買股票
  • s1:持有股票,當然可以出售
  • s2:未持有股票,不可購買股票

三種狀態之間的轉移

這裡寫圖片描述

Java程式碼:

class Solution {
    public int maxProfit(int[] prices) {
		 if(prices.length == 0) return 0;
		 int[] hasButNotBuy = new int[prices.length];
		 int[] notHasButCanBuy = new int[prices.length];
		 int[] notHasAndNotBuy = new int[prices.length];
		 hasButNotBuy[0] = -prices[0];
		 for(int i = 1; i < prices.length; i++) {
			 hasButNotBuy[i] = Math.max(hasButNotBuy[i - 1], notHasButCanBuy[i - 1] - prices[i]);
			 notHasButCanBuy[i] = Math.max(notHasButCanBuy[i - 1], notHasAndNotBuy[i - 1]);
			 notHasAndNotBuy[i] = hasButNotBuy[i] + prices[i];
		 }
		 return Math.max(notHasButCanBuy[prices.length - 1], notHasAndNotBuy[prices.length - 1]);
	 } 
}

在基本模型的基礎上,每一次交易都有手續費。

整個過程有兩種狀態,持有股票,未持有股票。

dp[ i ][ 0 ]表示在第i天未持有股票的最大收益,dp[ i ][ 1 ]表示在第i天持有股票的最大收益,轉移方程為:

dp[i][1]=max(dp[i-1][0]-prices[i],dp[i-1][1])

dp[i][0]=max(dp[i-1][1]+prices[i]-fee,dp[i-1][0])

Java程式碼,當然也可以降維。

public class Solution {
	  	public int maxProfit(int[] prices, int fee) {
	  		if(prices.length == 0) return 0;
	        int[][] dp = new int[prices.length][2];
	        dp[0][1] = -prices[0];
	        for(int i = 1; i < prices.length; i++) {
	        	dp[i][1] = Math.max(dp[i - 1][0] - prices[i], dp[i - 1][1]);
	        	dp[i][0] = Math.max(dp[i - 1][1] + prices[i] - fee, dp[i - 1][0]);
	        }
	        return dp[prices.length - 1][0];
	    }
}