LeetCode309. 最佳買賣股票時機含冷凍期
給定一個整數數組,其中第 i 個元素代表了第 i 天的股票價格 。設計一個算法計算出最大利潤。在滿足以下約束條件下,你可以儘可能地完成更多的交易(多次買賣一支股票):
- 你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。
- 賣出股票後,你無法在第二天買入股票 (即冷凍期爲 1 天)。
示例:
輸入: [1,2,3,0,2]
輸出: 3
解釋: 對應的交易狀態爲: [買入, 賣出, 冷凍期, 買入, 賣出]
定義狀態:我們設置一個數組dp[len][2],第二個維度爲1時表示此時持有股票,爲0表示未持有股票
狀態方程:
- dp[i][0] = max(prices[i] - dp[i - 1][1], dp[i - 1][0]); // 第i天不持有股票,此時我們來記錄先前拋售利潤的最大值
- case1:前一天買入股票,我們當天拋售;
- case2:前一天未買入股票。
- dp[i][1] = min(prices[i] - dp[i - 2][0], dp[i - 1][1]); // 第i天,持有股票,這時應讓持有的股票值最小,這樣相減時利潤最大
- case1:冷卻期結束也未買入(即前兩天爲冷卻期,前一天也未買入),我們當天買入;
- case2:前一天買入股票。
邊界情況:數組長度小於等於2的情況
初值定義:
- dp[0][0] = 0; // 第一天,不持有股票
- dp[0][1] = prices[0]; // 第一天,持有股票
- dp[1][0] = max(dp[0][0], prices[1] - dp[0][1]); // 第二天,不持有股票
- dp[1][1] = min(prices[1] - dp[0][0], dp[0][1]); // 第二天,持有股票
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
if(len <= 1) return 0; // 特殊情況處理
if(len == 2 && prices[0] >= prices[1]) return 0; // 特殊情況處理
else if(len == 2 && prices[0] < prices[1])
return prices[1] - prices[0]; // 特殊情況處理
vector<vector<int>> dp(len, vector<int>(2, 0));
dp[0][0] = 0; // 第一天,不持有股票
dp[0][1] = prices[0]; // 第一天,持有股票
dp[1][0] = max(dp[0][0], prices[1] - dp[0][1]); // 第二天,不持有股票
dp[1][1] = min(prices[1] - dp[0][0], dp[0][1]); // 第二天,持有股票
for(int i = 2; i < len; i++){
dp[i][0] = max(prices[i] - dp[i - 1][1], dp[i - 1][0]);
// 第i天,不持有股票,可能此時已拋售這時應讓利潤最大
dp[i][1] = min(prices[i] - dp[i - 2][0], dp[i - 1][1]);
// 第i天,持有股票,這時應讓持有的股票值最小
}
return dp[len - 1][0];
}
};
LeetCode123. 買賣股票的最佳時機 III
給定一個數組,它的第 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 。
狀態表示
與上一題類似用 dp(i, j) 表示第 i 天進行 j 次交易的最大利潤。
狀態屬性
可以看到題目要求是最大利潤,所以屬性就是求一個最大值。
狀態計算
這個是最難的一步,方法就是從 dp(i,j) 表示的 實際意義 出發,儘可能地劃分出所有的條件。比如本題,我們從關鍵詞第 i 天,第 j 次交易進行思考,不難發現第 i 天可以劃分兩種情況:
- 不交易:第 i 天什麼都沒發生,交易次數 j 不變,利潤爲前一天的利潤:dp(i, j)=dp(i − 1, j);
- 交易:利潤爲前 j−1 次交易的利潤與第 i 天交易的利潤之和:dp(i, j)=dp(k − 1, j − 1) + prices(i) − prices(k);
由於買入賣出爲一次交易,因此準確來講如果在第 i 天進行交易就是指在第 i 天進行賣出操作,那麼是何時買入的呢?如果買入爲第 k 天,則 k 滿足 k∈[1,...,i]。
算法
由於重複計算 k 會多嵌套一層循環導致代碼超時,因此需要將第一筆交易獲得的利潤整合到第二筆交易的成本中。
dp(i, j)=prices(i) − (prices(k) − dp(k − 1, j − 1));前者表示第 i 天賣出價格,後者表示第 k 天買入價格減去第1次交易利潤
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
if(len <= 1) return 0; // 特殊情況處理
if(len == 2 && prices[0] >= prices[1]) return 0; // 特殊情況處理
else if(len == 2 && prices[0] < prices[1])
return prices[1] - prices[0]; // 特殊情況處理
vector<vector<int>> dp(len, vector<int>(3, 0));
for(int j = 1; j <= 2 ; j++){
int minValue = prices[0];
for (int i = 1; i < len; i++){
minValue = min(minValue , prices[i] - dp[i - 1][j - 1]);
dp[i][j] = max(dp[i - 1][j], prices[i] - minValue);
}
}
return dp[len - 1][2];
}
};
LeetCode188. 買賣股票的最佳時機 IV
此題與上一題的不同點在於可以進行k次交易,但解題思路是一樣的,只不過當k >= len/2時,可退化爲無限次交易的問題。
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
int len = prices.size();
if(len <= 1) return 0; // 特殊情況處理
if (k >= len/2){ //當k >= len/2時,可退化爲無限次交易的問題
int res = 0;
for (int i = 1; i < len; i ++){
res += max(0, prices[i] - prices[i - 1]);
}
return res;
}
vector<vector<int>> dp(len, vector<int>(k + 1, 0));
for(int j = 1; j <= k ; j++){
int minValue = prices[0];
for (int i = 1; i < len; i++){
minValue = min(minValue , prices[i] - dp[i][j - 1]);
dp[i][j] = max(dp[i - 1][j], prices[i] - minValue);
}
}
return dp[len - 1][k];
}
};
LeetCode312. 戳氣球
有 n 個氣球,編號爲0 到 n-1,每個氣球上都標有一個數字,這些數字存在數組 nums 中。現在要求你戳破所有的氣球。每當你戳破一個氣球 i 時,你可以獲得 nums[left] * nums[i] * nums[right] 個硬幣。 這裏的 left 和 right 代表和 i 相鄰的兩個氣球的序號。注意當你戳破了氣球 i 後,氣球 left 和氣球 right 就變成了相鄰的氣球。求所能獲得硬幣的最大數量。
說明:
你可以假設 nums[-1] = nums[n] = 1,但注意它們不是真實存在的所以並不能被戳破。
0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100
示例:
輸入: [3,1,5,8]
輸出: 167
解釋: nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> []
coins = 3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 167
狀態表示: f[i][j] 表示戳破 (i, j) 之間所有氣球的集合,屬性(最大值)這題的狀態表示很巧妙,用到了開區間
狀態計算:設 i 和 j 之間最後一個被戳破的氣球爲 k,那此時 (i, k) 和 (k, j) 之間的氣球已被戳破,最後 (i, j) 之間只剩下氣球 k ,相鄰的就是氣球 i 和 j,當j − i >= 2時,f[i][j] = f[i][k] + f[k][j] + p[i]∗p[k]∗p[j]
class Solution {
public:
int maxCoins(vector<int>& nums) {
int length = nums.size();
vector<int> tmp(length + 2, 1);
vector<vector<int>> dp(length + 2, vector<int>(length + 2, 0));
for (int i = 1; i <= length; i++)
tmp[i] = nums[i - 1]; //[3,1,5,8]變成[1,3,1,5,8,1]
for (int len = 1; len <= length + 2; len ++) { //len的範圍[3,1,5,8,1]
for (int i = 0; i + len - 1 <= length + 1; i++) { //i爲左邊界範圍[1,3,1,5,8]
int j = i + len - 1; //j爲右邊界
if (len == 1 || len == 2) dp[i][j] = 0; //意味着右邊界至少比左邊界大2
for (int k = i + 1; k <= j - 1; k++) {
//k爲最後戳破的氣球,此時 (i,k) 和 (k,j)之間的氣球已被戳破
//最後 (i,j) 之間只剩下氣球 k
dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + tmp[i] * tmp[k] * tmp[j]);
}
}
}
return dp[0][length + 1];
}
};