本文首發於公衆號「五分鐘學算法」,是圖解 LeetCode 系列文章之一。java
我的網站:https://www.cxyxiaowu.comgit
動態規劃算法是經過拆分問題,定義問題狀態和狀態之間的關係,使得問題可以以遞推(或者說分治)的方式去解決。在學習動態規劃以前須要明確掌握幾個重要概念。github
階段:對於一個完整的問題過程,適當的切分爲若干個相互聯繫的子問題,每次在求解一個子問題,則對應一個階段,整個問題的求解轉化爲按照階段次序去求解。web
狀態:狀態表示每一個階段開始時所處的客觀條件,即在求解子問題時的已知條件。狀態描述了研究的問題過程當中的情況。算法
決策:決策表示當求解過程處於某一階段的某一狀態時,能夠根據當前條件做出不一樣的選擇,從而肯定下一個階段的狀態,這種選擇稱爲決策。數組
策略:由全部階段的決策組成的決策序列稱爲全過程策略,簡稱策略。app
最優策略:在全部的策略中,找到代價最小,性能最優的策略,此策略稱爲最優策略。性能
狀態轉移方程:狀態轉移方程是肯定兩個相鄰階段狀態的演變過程,描述了狀態之間是如何演變的。學習
能採用動態規劃求解的問題的通常要具備 3 個性質:優化
(1)最優化:若是問題的最優解所包含的子問題的解也是最優的,就稱該問題具備最優子結構,即知足最優化原理。子問題的局部最優將致使整個問題的全局最優。換句話說,就是問題的一個最優解中必定包含子問題的一個最優解。
(2)無後效性:即某階段狀態一旦肯定,就不受這個狀態之後決策的影響。也就是說,某狀態之後的過程不會影響之前的狀態,只與當前狀態有關,與其餘階段的狀態無關,特別是與未發生的階段的狀態無關。
(3)重疊子問題:即子問題之間是不獨立的,一個子問題在下一階段決策中可能被屢次使用到。(該性質並非動態規劃適用的必要條件,可是若是沒有這條性質,動態規劃算法同其餘算法相比就不具有優點)
(1)劃分階段:按照問題的時間或者空間特徵將問題劃分爲若干個階段。
(2)肯定狀態以及狀態變量:將問題的不一樣階段時期的不一樣狀態描述出來。
(3)肯定決策並寫出狀態轉移方程:根據相鄰兩個階段的各個狀態之間的關係肯定決策。
(4)尋找邊界條件:通常而言,狀態轉移方程是遞推式,必須有一個遞推的邊界條件。
(5)設計程序,解決問題
下面的三道算法題都是來源於 LeetCode 上與股票買賣相關的問題 ,咱們按照 動態規劃 的算法流程來處理該類問題。
股票買賣這一類的問題,都是給一個輸入數組,裏面的每一個元素表示的是天天的股價,而且你只能持有一支股票(也就是你必須在再次購買前出售掉以前的股票),通常來講有下面幾種問法:
須要你設計一個算法去獲取最大的利潤。
題目來源於 LeetCode 上第 121 號問題:買賣股票的最佳時機。題目難度爲 Easy,目前經過率爲 49.4% 。
給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。
若是你最多隻容許完成一筆交易(即買入和賣出一支股票),設計一個算法來計算你所能獲取的最大利潤。
注意你不能在買入股票前賣出股票。
示例 1:
輸入: [7,1,5,3,6,4]
輸出: 5
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 5 天(股票價格 = 6)的時候賣出,最大利潤 = 6-1 = 5 。
注意利潤不能是 7-1 = 6, 由於賣出價格須要大於買入價格。
示例 2:
輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種狀況下, 沒有交易完成, 因此最大利潤爲 0。
咱們按照動態規劃的思想來思考這道問題。
有 買入(buy) 和 賣出(sell) 這兩種狀態。
對於買來講,買以後能夠賣出(進入賣狀態),也能夠再也不進行股票交易(保持買狀態)。
對於賣來講,賣出股票後不在進行股票交易(還在賣狀態)。
只有在手上的錢纔算錢,手上的錢購買當天的股票後至關於虧損。也就是說當天買的話意味着損失-prices[i]
,當天賣的話意味着增長prices[i]
,當天賣出總的收益就是 buy+prices[i]
。
因此咱們只要考慮當天買和以前買哪一個收益更高,當天賣和以前賣哪一個收益更高。
第一天 buy = -prices[0]
, sell = 0
,最後返回 sell 便可。
class Solution {
public int maxProfit(int[] prices) {
if(prices.length <= 1)
return 0;
int buy = -prices[0], sell = 0;
for(int i = 1; i < prices.length; i++) {
buy = Math.max(buy, -prices[i]);
sell = Math.max(sell, prices[i] + buy);
}
return sell;
}
}
題目來源於 LeetCode 上第 122 號問題:買賣股票的最佳時機 II。題目難度爲 Easy,目前經過率爲 53.0% 。
給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。
設計一個算法來計算你所能獲取的最大利潤。你能夠儘量地完成更多的交易(屢次買賣一支股票)。
注意:你不能同時參與多筆交易(你必須在再次購買前出售掉以前的股票)。
示例 1:
輸入: [7,1,5,3,6,4]
輸出: 7
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 3 天(股票價格 = 5)的時候賣出, 這筆交易所能得到利潤 = 5-1 = 4 。
隨後,在第 4 天(股票價格 = 3)的時候買入,在第 5 天(股票價格 = 6)的時候賣出, 這筆交易所能得到利潤 = 6-3 = 3 。
示例 2:
輸入: [1,2,3,4,5]
輸出: 4
解釋: 在第 1 天(股票價格 = 1)的時候買入,在第 5 天 (股票價格 = 5)的時候賣出, 這筆交易所能得到利潤 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接連購買股票,以後再將它們賣出。
由於這樣屬於同時參與了多筆交易,你必須在再次購買前出售掉以前的股票。
示例 3:
輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種狀況下, 沒有交易完成, 因此最大利潤爲 0。
有 買入(buy) 和 賣出(sell) 這兩種狀態。
對比上題,這裏能夠有無限次的買入和賣出,也就是說 買入 狀態以前可擁有 賣出 狀態,因此買入的轉移方程須要變化。
第一天 buy = -prices[0]
, sell = 0
,最後返回 sell 便可。
class Solution {
public int maxProfit(int[] prices) {
if(prices.length <= 1)
return 0;
int buy = -prices[0], sell = 0;
for(int i = 1; i < prices.length; i++) {
sell = Math.max(sell, prices[i] + buy);
buy = Math.max( buy,sell - prices[i]);
}
return sell;
}
}
題目來源於 LeetCode 上第 123 號問題:買賣股票的最佳時機 III。題目難度爲 Hard,目前經過率爲 36.1% 。
給定一個數組,它的第 i 個元素是一支給定的股票在第 i 天的價格。
設計一個算法來計算你所能獲取的最大利潤。你最多能夠完成 兩筆 交易。
注意: 你不能同時參與多筆交易(你必須在再次購買前出售掉以前的股票)。
示例 1:
輸入: [3,3,5,0,0,3,1,4]
輸出: 6
解釋: 在第 4 天(股票價格 = 0)的時候買入,在第 6 天(股票價格 = 3)的時候賣出,這筆交易所能得到利潤 = 3-0 = 3 。
隨後,在第 7 天(股票價格 = 1)的時候買入,在第 8 天 (股票價格 = 4)的時候賣出,這筆交易所能得到利潤 = 4-1 = 3 。
示例 2:
輸入: [1,2,3,4,5]
輸出: 4
解釋: 在第 1 天(股票價格 = 1)的時候買入,在第 5 天 (股票價格 = 5)的時候賣出, 這筆交易所能得到利潤 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接連購買股票,以後再將它們賣出。
由於這樣屬於同時參與了多筆交易,你必須在再次購買前出售掉以前的股票。
示例 3:
輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這個狀況下, 沒有交易完成, 因此最大利潤爲 0。
這裏限制了最多兩筆交易。
有 第一次買入(fstBuy) 、 第一次賣出(fstSell)、第二次買入(secBuy) 和 第二次賣出(secSell) 這四種狀態。
這裏最多兩次買入和兩次賣出,也就是說 買入 狀態以前可擁有 賣出 狀態,賣出 狀態以前可擁有 買入 狀態,因此買入和賣出的轉移方程都須要變化。
fstBuy = -prices[0]
fstSell = 0
secBuy - prices[0]
secSell = 0
最後返回 secSell 。
class Solution {
public int maxProfit(int[] prices) {
int fstBuy = Integer.MIN_VALUE, fstSell = 0;
int secBuy = Integer.MIN_VALUE, secSell = 0;
for(int i = 0; i < prices.length; i++) {
fstBuy = Math.max(fstBuy, -prices[i]);
fstSell = Math.max(fstSell, fstBuy + prices[i]);
secBuy = Math.max(secBuy, fstSell - prices[i]);
secSell = Math.max(secSell, secBuy + prices[i]);
}
return secSell; }}