PHP求解股票買賣問題

動態規劃比較經典的題目

給定一支股票一段日期內的的每日價格price


題目1: 求這段時間內買賣一次獲得的最大收益

思路:即找到一組i和j,使得 price[i] - price[j] 最大,且i<j

狀態轉移方程式爲:profit = max(profit, price[i] - minPrice);

minPrice = min(minPrice, price[i]);

function solution1($price) {
	if(!$price)
		return 0;
	
	$profit = 0;
	$min = $price[0];
	
	for ($i = 1; $i < count($price); $i++) {
		$min = min($min, $price[$i]);
		$profit = max($profit, $price[$i] - $min);
	}
	
	return $profit;
}


題目2: 求這段時間內無限次買賣可獲得的最大收益

思路一、動態規劃法:對某一時間點i,有兩種狀態,持有或不持有,那麼同理在i-1天也有這兩種狀態,用hold表示在i天持有的狀態下的最大收益,free表示在i天不持有的最大收益,狀態轉移方程式就可以寫爲

hold[i] = max(hold[i - 1], free[i - 1] - price[i]); //持有狀態有兩種情況:前一天就持有的收益和前一天空倉當天買入的收益做比較

free[i] = max(free[i - 1], hold[i - 1] + price[i]);//不持有有兩種情況:前一天就不持有的收益和前一天持有當天賣出的收益做比較

狀態初始條件爲:

hold[0] = -price[0];

free[0] = 0;


狀態轉移方程式可以優化空間效率,因爲當前收益總是單純依賴前一天的收益,因此可寫爲:

hold = max(hold, free - price[i]);

free = max(free, hold + price[i]);

function solution2($price) {
	if(!$price)
		return 0;
	
	$hold = -$price[0];
	$free = 0;
	for ($i = 1; $i < count($price); $i++) {
		$pre_hold = $hold;
		$hold = max($hold, $free - $price[$i]);
		$free = max($free, $pre_hold + $price[$i]);
	}
	return $free;
}

思路二、貪心法

只要後一天比前一天貴,就賣出,比較容易理解


題目3: 帶冷卻時間的購買,即賣出後必須等待一天,才能再次購買

思路一

這裏有兩種解題思路,先看比較簡單的一種,與題目2基本一致,只是第i天的持有狀態要從第i-2天轉移來而不是i-1天,狀態轉移公式改爲:

hold[i] = max(hold[i - 1], free[i - 2] - price[i]); 

free[i] = max(free[i - 1], hold[i - 1] + price[i]);

狀態初始條件要改爲:

hold[0] = -price[0];

hold[1] = max(hold[0], -price[1]);

free[0] = 0;

free[1] = max(free[0], hold[0]+price[1]);


做一下空間效率優化

hold = max(hold, pre_free - price[i]); //pre_free不是前一天,而是前兩天,在狀態轉移方程式等號右邊中的free本身就是前一天的狀態

free = max(free, hold + price[i]);

狀態初始條件:

hold = max(-price[0], -price[1]);

pre_free = 0;

free = max(pre_free, hold + price[1]);

function solution4($price) {
	if(!$price)
		return 0;
	
	$hold = max(-$price[0], -$price[1]);
	$pre_free = 0;
	$free = max($pre_free, $hold+$price[1]);
	for ($i = 2; $i < count($price); $i++) {
		$pre_hold = $hold;
		$hold = max($hold, $pre_free - $price[$i]);
		$pre_free = $free;
		$free = max($free, $pre_hold + $price[$i]);
	}
	return $free;
}

思路二

第二種思路引入了rest這種狀態,即冷卻日期,好在rest的前一狀態必爲持有狀態hold,狀態機如圖


因此狀態轉移方程式即爲(這裏直接寫空間優化後的):

free = max(free, rest); //stay at s0, or rest from s2

hold = max(hold, free - price[i]); //stay at s1, or buy from s0

rest = hold + price[i]; // only one way from s1

狀態初始條件爲:

hold = -price[0];

free = 0;

rest = 0;

function solution3($price) {
	if(!$price)
		return 0;
	
	$free = 0;
	$hold = -$price[0];
	$rest = 0;
	for ($i = 1; $i < count($price); $i++) {
		$pre_hold = $hold;
		$pre_free = $free;
		$free = max($free, $rest);
		$hold = max($hold, $pre_free - $price[$i]);
		$rest = $pre_hold + $price[$i];
	}
	return max($free, $rest);
}


題目4: 求這段時間內最多交易兩次的最大收益

思路:由於題目限制在買入之前必須未持有,因此不會產生交叉交易的情況,我們可以找到一箇中間點,分成兩段分別計算最大收益,轉化爲兩個與題目1一樣的子問題。

假設總共有n天preProfit[i]表示從0到i天的最大收益,postProfit[i] 表示從i天到n天的最大收益。有狀態轉移方程式:

preProfit[i] = max(preProfit[i-1], price[i] - minPrice);

postProfit[i] = max(postProfit[i+1], maxPrice - price[i]);

最後再同時遍歷兩數組,max(preProfit[i]+postProfit[i]);即爲所求

function solution5($price) {
	if(!$price)
		return 0;
	
	$pre_profit = array(0);
	$post_profit = array();
	$post_profit[count($price) - 1] = 0;
	
	$min = $price[0];
	for ($i = 1; $i < count($price); $i++) {
		$min = min($min, $price[$i]);
		$pre_profit[$i] = max($pre_profit[$i - 1], $price[$i] - $min);
	}
	
	$max = $price[count($price) - 1];
	for ($i = count($price) - 2; $i >= 0; $i--){
		$max = max($max, $price[$i]);
		$post_profit[$i] = max($post_profit[$i + 1], $max - $price[$i]);
	}
	
	$max_profit = 0;
	for ($i = 0; $i < count($price); $i++) {
		$max_profit = max($max_profit, $pre_profit[$i] + $post_profit[$i]);
	}
	return $max_profit;
}


題目5: 求這段時間內最多交易k次的最大收益

思路一:參照Code Ganker 的思路,維護兩個變量,原文爲local和global,local[i][j]表示第i天一定有交易發生,globlal即爲第i天最多進行k次交易的最大收益,其實我覺得用mustSell描述local更容易理解些,狀態轉移方程式爲:

mustSell[i][j] = max(global[i – 1][j – 1]+max(0, profit) , mustSell[i – 1][j] +profit);//profit = prices[i] – prices[i – 1];

global[i][j] = max(global[i – 1][j], mustSell[i][j]);

mustSell 的值在 前一天的最大收益global[i – 1][j – 1]+max(0, 當天賣出的收益) 和 前一天必須賣出+當天的收益(注意mustSell[i – 1][j]這裏並不是j-1,即表示合併前一天的買賣爲一次)中取大值

global很好理解,就是第i天賣與不賣之間取最大值

function solution6($price, $k){
	if(!$price)
		return 0;
	if($k > count($price)/2)
		return solution2($price);
	
	$mustSell = array();
	$global = array();
	for ($i = 1; $i < count($price); $i++) {
		$profit = $price[$i] - $price[$i - 1];
		for ($j = 1; $j <= $k; $j++) {
			$mustSell[$i][$j] = max($global[$i - 1][$j - 1]+max(0, $profit), $mustSell[$i - 1][$j] + $profit);
			$global[$i][$j] = max($global[$i - 1][$j], $mustSell[$i][$j]);
		}
	}
	return $global[count($price) - 1][$k];
}

當k > I/2時,問題退化爲題目2


思路二、滾動掃描

我們並不需要知道每個時間點買賣收益的全部信息,只需要知道每個時間點的最大收益信息就可以了。我們用free[k]表示在第k次交易賣出的最大收益,hold[k]表示在第k次交易買入的最大交易,狀態轉移方程式:

free[k] = max(free[k], hold[k] + price[k]);
hold[k] = max(hold[k], free[k-1] - price[k]);

function solution7($price, $k) {
	if(!$price)
		return 0;
	if($k > count($price)/2)
		return solution2($price);
	
	$free = array(0);
	for ($i = 0; $i <= $k; $i++) {
		$hold[$i] = -INF;
	}
	for ($i = 0; $i < count($price); $i++) {
		for ($j = 1; $j <= $k; $j++) {
			$free[$j] = max($free[$j], $hold[$j] + $price[$i]);
			$hold[$j] = max($hold[$j], $free[$j-1] - $price[$i]);
		}
	}
	return $free[$k];
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章