Algorithm之動態規劃之最佳買股票時機

[color=green][size=medium][b]動態規劃(DP: Dynamic Programming)[/b][/size][/color]

[size=medium][b]一、基本思想與策略[/b][/size]

基本思想與分治法類似,也是將待求解的問題
- 分解爲若干個子問題(階段)
- 按順序求解子問題,
- 前一個(或前有限個,一般不超過三個)子問題的解,爲後一子問題的求解提供了有用的信息。
- 在求解子問題時,需要求得各種可能的解。這些解是累計關係,是當前子問題的最終解。
- 依次解決各子問題,最後一個子問題的解就是初始問題的解。

動態規劃的子問題是可以用來判定結果的最短子問題,或是前後兩個,或是前後三個,總之是有限個。
由於動態規劃解決的問題多數有重疊子問題這個特點,爲減少重複計算,對每一個子問題只解一次,
可將子問題及其輸出保存在一個二維數組中。

與分治法最大的差別是:
適合於用動態規劃法求解的問題,經分解後得到的子問題往往不是互相獨立的,
即下一個子問題的求解是建立在上一個(幾個)子問題的解的基礎上進行求解。


[size=medium][b]二、動態規劃過程[/b][/size]

每次決策需要依賴於以前的狀態,隨即又產生新的狀態。
每步都選擇最優狀態,但是其它狀態也需要保留,以便爲後面的決策提供依據。
一個決策序列就是在變化的狀態中產生出來的,所以這種多階段最優化決策解決問題的過程就稱爲動態規劃。


[size=medium][b]三、例子:最佳買股票時機(三種情況)[/b][/size]

[b]1、Best Time to Buy and Sell Stock -- LeetCode[/b]
http://blog.csdn.net/linhuanmars/article/details/23162793

求進行一次交易能得到的最大利潤。
用“局部最優和全局最優解法”。
思路是維護兩個變量,一個是到目前爲止最好的交易,另一個是今天買明天賣的最佳交易。
這樣一次掃描就可以得到結果,時間複雜度是O(n)。而空間只需要兩個變量,即O(1)。


public int maxProfit(int[] prices) {
if(prices==null || prices.length==0) return 0;
int local = 0;
int global = 0;
for(int i=0;i<prices.length-1;i++){
local = Math.max(local+prices[i+1]-prices[i],0);
global = Math.max(local, global);
}
return global;
}


這種題目的解法非常經典,不過是比較簡單的動態規劃。


[b]2、Best Time to Buy and Sell Stock II -- LeetCode[/b]
http://blog.csdn.net/linhuanmars/article/details/23164149

可以交易任意次數。
只要對每次兩天差價大於0的都進行交易,就可以得到最大利潤。
因此算法其實就是累加所有大於0的差價既可以了。



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




[b]3、Best Time to Buy and Sell Stock III -- LeetCode[/b]
http://blog.csdn.net/linhuanmars/article/details/23236995

最多可以進行K次交易。
還是使用“局部最優和全局最優解法”。
對於天數需要一次掃描,而每次要對交易次數進行遞推式求解,所以時間複雜度是O(n*k)。

下面是 K = 2 的情況:

public int maxProfit(int[] prices){
if(prices==null || prices.length==0) return 0;
int[] local = new int[3];
int[] global = new int[3];
for(int i=0;i<prices.length-1;i++){
int diff = prices[i+1]-prices[i];
for(int j=2;j>=1;j--){
local[j] = Math.max(global[j-1]+(diff>0?diff:0), local[j]+diff);
global[j] = Math.max(local[j],global[j]);
}
}
return global[2];
}


完善版本:


/*
* j -- max # of transactions; i --- # of days;
* local[i][j] -- profit achieved when selling at last day;
* global[i][j] --- profit achieved at all situations;
* transition equation:
* local[i][j] = max(local[i - 1][j], global[i - 1][j - 1]) + diff;
* (merge with previous transactions or NOT)
* global[i][j] = max(local[i][j], global[i - 1][j]);
*/
public class SolutionIV {
public int maxProfit(int k, int[] prices) {
int N = prices.length;
if (k >= N)
return simpleMaxProfit(prices);
int[] local = new int[k + 1], global = new int[k + 1];
int[] prevLocal = new int[k + 1], prevGlobal = new int[k + 1];
for (int i = 1; i < N; ++i) {
prevLocal = local; prevGlobal = global;
local = new int[k + 1]; global = new int[k + 1];
int diff = prices[i] - prices[i - 1];
for (int j = 1; j <= k; ++j) {
local[j] = Math.max(prevGlobal[j - 1], prevLocal[j]) + diff;
global[j] = Math.max(local[j], prevGlobal[j]);
}
}
return global[k];
}
int simpleMaxProfit(int[] prices) {
int N = prices.length;
if (N <= 1)
return 0;
int sum = 0;
for (int i = 1; i < N; i++) {
int diff = prices[i] - prices[i - 1];
if (diff > 0)
sum += diff;
}
return sum;
}
public static void main(String[] args) {
SolutionIV sol = new SolutionIV();
int[] nums = {4, 6, 1, 1, 4, 2, 5};
System.out.println(sol.maxProfit(2, nums));
}
}




[size=medium][b]四、例子:買股票的最佳時機[/b][/size]




package solution;

import org.junit.Test;

public class BestTimetoBuyAndSellStockwithCooldown {


// 最佳買股票時間有多種玩法和限制。
// 本例只舉其一種。
/*
LeetCode :
https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-cooldown
309. Best Time to Buy and Sell Stock with Cooldown
*
Say you have an array for which the ith element is the price of a given stock
on day i.

Design an algorithm to find the maximum profit. You may complete as many
transactions as you like (ie, buy one and sell one share of the stock multiple
times) with the following restrictions:

- You may not engage in multiple transactions at the same time
(ie, you must sell the stock before you buy again).

- After you sell your stock, you cannot buy stock on next day.
(ie, cooldown 1 day)

Example:

prices = [1, 2, 3, 0, 2]
maxProfit = 3
transactions = [buy, sell, cooldown, buy, sell]

*/

@Test
public void testBuyandSell(){
int[] prices = new int[] {1, 2, 7, 0, 2, 7, 100};
System.out.println(maxProfit(prices));
}

/*
* 分析:
*------------------------------------------------------------------------------
*
* 對於每天來講:只會出現在這三種狀態之中:buy, sell, rest
*
* buy[i] means before day i what is the maxProfit for any sequence end with buy.
* 計算今天狀態爲 buy 時的最好收益:
* buy[i] = max(rest[i-1]-price, buy[i-1])
* 買入的狀態 = max(昨天凍結今天買,昨天已買入)
*
* sell[i] means before day i what is the maxProfit for any sequence end with sell.
* 記算今天狀態爲 sell 時的最好收益:
* sell[i] = max(buy[i-1]+price, sell[i-1])
* 賣出的狀態 = max(昨天爲買今天賣出,昨天已賣出)
*
* rest[i] means before day i what is the maxProfit for any sequence end with rest.
* 記算今天狀態爲 rest 時的最好收益:
* rest[i] = sell[i-1]
*
* -------------------------------------------------------------------------------------

The series of problems are typical dp. The key for dp is to find the variables
to represent the states and deduce the transition function.

Of course one may come up with a O(1) space solution directly,
but I think it is better to be generous when you think and be greedy when you
implement.

The natural states for this problem is the 3 possible transactions :
buy, sell, rest.

Here rest means no transaction on that day (aka cooldown).
Then the transaction sequences can end with any of these three states.
For each of them we make an array, buy[n], sell[n] and rest[n].

buy[i]
means before day i what is the maxProfit for any sequence end with buy.

sell[i]
means before day i what is the maxProfit for any sequence end with sell.

rest[i]
means before day i what is the maxProfit for any sequence end with rest.

Then we want to deduce the transition functions for buy sell and rest.
By definition we have:

buy[i] = max(rest[i-1]-price, buy[i-1])
sell[i] = max(buy[i-1]+price, sell[i-1])
rest[i] = max(sell[i-1], buy[i-1], rest[i-1])

Where price is the price of day i. All of these are very straightforward.
They simply represents :

(1) We have to `rest` before we `buy` and
(2) we have to `buy` before we `sell`
One tricky point is how do you make sure you sell before you buy, since
from the equations it seems that [buy, rest, buy] is entirely possible.

Well, the answer lies within the fact that buy[i] <= rest[i] which means
rest[i] = max(sell[i-1], rest[i-1]). That made sure [buy, rest, buy] is
never occurred.

A further observation is that and rest[i] <= sell[i] is also true
therefore:

rest[i] = sell[i-1]

Substitute this in to buy[i] we now have 2 functions instead of 3:

buy[i] = max(sell[i-2]-price, buy[i-1])
sell[i] = max(buy[i-1]+price, sell[i-1])

This is better than 3, but we can do even better

Since states of day i relies only on i-1 and i-2 we can reduce
the O(n) space to O(1).

*/

private int maxProfit(int[] prices) {
int sell = 0, prev_sell = 0, buy = Integer.MIN_VALUE, prev_buy;
for (int price : prices) {
prev_buy = buy;
buy = Math.max(prev_sell - price, prev_buy);
prev_sell = sell;
sell = Math.max(prev_buy + price, prev_sell);
}
return sell;
}


}





引用:
https://zh.wikipedia.org/zh-tw/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92

http://www.cnblogs.com/steven_oyj/archive/2010/05/22/1741374.html

https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/

五大常用算法之二:動態規劃算法
http://www.cnblogs.com/steven_oyj/archive/2010/05/22/1741374.html


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