LeetCode123——買賣股票的最佳時機III
題目描述:
知識點:動態規劃
列舉第一筆交易賣出的時間firstDealSell,對[0, firstDealSell]範圍內的價格求解LeetCode121——買賣股票的最佳時機中的問題,得到結果result1,再對[firstDealSell, n - 1]範圍內的價格再一次求解LeetCode121——買賣股票的最佳時機中的問題,其中n為prices陣列的大小,得到結果result2,求result1 + result2的最大值。
時間複雜度是O(n ^ 2)。空間複雜度是O(1)。
對於第一次和第二次賣出的時間點,firstDealSell和secondDealSell,其之前的價格一定是一個上坡,其之後的價格一定是一個下坡,我們在價格坡頂賣出。
JAVA程式碼:
public class Solution { public int maxProfit(int[] prices) { int result = 0; if(prices.length == 0){ return result; } int firstDealSell; //第一筆交易在firstDealSell賣出 int secondDealSell; //第二筆交易在secondDealSell賣出 for(secondDealSell = prices.length - 1; secondDealSell > 0; secondDealSell--){ if(prices[secondDealSell - 1] < prices[secondDealSell]){ break; } } for(firstDealSell = 1; firstDealSell < prices.length; firstDealSell++){ while(firstDealSell + 1 < prices.length && prices[firstDealSell + 1] >= prices[firstDealSell]){ firstDealSell++; } int result1 = maxProfit(prices, 0, firstDealSell); int result2 = maxProfit(prices, firstDealSell + 1, secondDealSell); if(result1 + result2 > result){ result = result1 + result2; } } return result; } private int maxProfit(int[] prices, int left, int right){ int result = 0; if(right - left < 1){ return result; } int minPrice = prices[left]; for(int i = left + 1; i <= right; i++){ result = Math.max(result, prices[i] - minPrice); minPrice = Math.min(minPrice, prices[i]); } return result; } }
LeetCode解題報告:
思路二:動態規劃
狀態定義:f(x, y) -------- 第x筆交易在第y天能取得的最大利潤
狀態轉移:
(1)當x == 1時
a:我們可以選擇在第y天不賣出也不買入,
a-1:如果此時y == 0,即第0天,那麼f(1, 0) = 0,即取得的最大利潤為0。
a-2:如果此時y > 0,那麼此時我們的第x筆交易在第y天能取得的最大利潤是f(1, y - 1),因為我們在第y天既不買入也不賣出,取得的最大利潤自然和第x筆交易在第y - 1天能取得的最大利潤相同。
b:我們可以選擇在第y天賣出,
b-1:如果此時y == 0,顯然,我們不可能在第0天賣出,這種情況不予討論。
b-2:如果此時y > 0,f(x, y) = max(prices[y] - prices[b]),其中0 <= b < y,代表我們在第b天買入。
綜上,對於x == 1的情況,當y == 0時,f(1, 0) = 0;當y > 0時,f(1, y) = max(f(x, y - 1), prices[y] - prices[b]),0 <= b < y。
(2)當x == 2時
a:我們可以選擇在第y天不賣出也不買入,
a-1:如果此時y == 0,即第0天,那麼f(x, y) = 0,即取得的最大利潤為0。
a-2:如果此時y > 0,那麼此時我們的第x筆交易在第y天能取得的最大利潤是f(2, y - 1),因為我們在第y天既不買入也不賣出,取得的最大利潤自然和第x筆交易在第y - 1天能取得的最大利潤相同。
b:我們可以選擇在第y天賣出,
b-1:如果此時y == 0,顯然,我們不可能在第0天賣出,這種情況不予討論。
b-2:如果此時y > 0,f(x, y) = max(prices[y] - prices[b] + f(1, b - 1)),其中0 <= b < y,代表我們在第b天買入。
綜上,對於x == 2的情況,當y == 0時,f(2, 0) = 0;當y > 0時,f(2, y) = max(f(x, y - 1), prices[y] - prices[b] + f(1, b - 1)),0 <= b < y。
時間複雜度是O(kn ^ 2),其中k為交易次數,n為prices陣列的大小。空間複雜度是O(kn)。
JAVA程式碼:
public class Solution {
public int maxProfit(int[] prices) {
int result = 0;
if(0 == prices.length){
return result;
}
int[][] dp = new int[2][prices.length];
for(int k = 0; k < 2; k++){
dp[k][0] = 0;
int min = prices[0];
for(int i = 1; i < prices.length; i++){
for(int b = 1; b < i; b++){
if(k == 0){
min = Math.min(min, prices[b]);
}else{
min = Math.min(min, prices[b] - dp[k - 1][b - 1]);
}
}
dp[k][i] = Math.max(dp[k][i - 1], prices[i] - min);
}
}
return dp[1][prices.length - 1];
}
}
LeetCode解題報告:
思路三:對思路二的改進
在思路二中,我們對第一筆交易和第二筆交易進行了分別計算,因為第1筆交易的前一筆交易不存在,會產生陣列越界問題。
我們可以在第一筆交易前增加一筆虛擬的第0筆交易,其f(0, y)均為0,這樣就避免了對第一筆交易和第二筆交易的討論。
同時,我們會發現思路二中min的計算其實重複進行了,我們完全可以忽略內層的迴圈變數b的迴圈。
時間複雜度和空間複雜度均是O(kn),其中k為交易次數,n為prices陣列的大小。
JAVA程式碼:
public class Solution {
public int maxProfit(int[] prices) {
int result = 0;
if (0 == prices.length) {
return result;
}
int[][] dp = new int[3][prices.length];
for(int k = 1; k <= 2; k++){
dp[k][0] = 0;
int min = prices[0];
for(int i = 1; i < prices.length; i++){
dp[k][i] = Math.max(dp[k][i - 1], prices[i] - min);
min = Math.min(min, prices[i] - dp[k - 1][i - 1]);
}
}
return dp[2][prices.length - 1];
}
}
LeetCode解題報告:
思路四:思路三的另一種形式
交換內外層迴圈的位置。
時間複雜度和空間複雜度均是O(kn),其中k為交易次數,n為prices陣列的大小。
JAVA程式碼:
public class Solution {
public int maxProfit(int[] prices) {
int result = 0;
if (0 == prices.length) {
return result;
}
int[][] dp = new int[3][prices.length];
int[] min = new int[3];
for(int i = 1; i < 3; i++){
min[i] = prices[0];
}
for(int i = 1; i < prices.length; i++){
for(int k = 1; k <= 2; k++){
dp[k][i] = Math.max(dp[k][i - 1], prices[i] - min[k]);
min[k] = Math.min(min[k], prices[i] - dp[k - 1][i - 1]);
}
}
return dp[2][prices.length - 1];
}
}
LeetCode解題報告:
思路五:思路四的改進
從思路四中我們發現第i列的變數只依賴於第i - 1列的變數,壓縮該維度。
時間複雜度是O(k),其中k為交易次數,n為prices陣列的大小。空間複雜度是O(k)。
JAVA程式碼:
public class Solution {
public int maxProfit(int[] prices) {
int result = 0;
if (0 == prices.length) {
return result;
}
int[] dp = new int[3];
int[] min = new int[3];
for(int i = 1; i < 3; i++){
min[i] = prices[0];
}
for(int i = 1; i < prices.length; i++){
for(int k = 1; k <= 2; k++){
dp[k] = Math.max(dp[k], prices[i] - min[k]);
min[k] = Math.min(min[k], prices[i] - dp[k - 1]);
}
}
return dp[2];
}
}
LeetCode解題報告:
思路六:思路五的改進
針對本題,只進行2次交易,優化程式碼。
時間複雜度是O(1),其中k為交易次數,n為prices陣列的大小。空間複雜度是O(1)。
JAVA程式碼:
public class Solution {
public int maxProfit(int[] prices) {
int buy1 = Integer.MAX_VALUE;
int sell1 = 0;
int buy2 = Integer.MAX_VALUE;
int sell2 = 0;
for(int i = 0; i < prices.length; i++){
sell1 = Math.max(sell1, prices[i] - buy1);
buy1 = Math.min(buy1, prices[i]);
sell2 = Math.max(sell2, prices[i] - buy2);
buy2 = Math.min(buy2, prices[i] - sell1);
}
return sell2;
}
}
LeetCode解題報告: