上來直接最難的,後面都是毛毛雨
問題一:買賣股票的最佳時機IV(leetcode188)
問題描述:
給定一個數組,它的第 i 個元素是一支給定的股票在第 i 天的價格。
設計一個算法來計算你所能獲取的最大利潤。你最多可以完成 k 筆交易。
注意: 你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv
著作權歸領釦網絡所有。商業轉載請聯繫官方授權,非商業轉載請註明出處。
大體思路:
二維數組dp0[i][j]表示在第i天,最大可交易次數爲j時不持有股票的最大收益;
同理dp1[i][j]表示在第天,最大可交易次數爲j時持有股票的最大收益。i從0到最大天數,j從0到k。
dp0[i][j] = max(dp0[i - 1][j], dp1[i - 1][j] + prices[i])
dp1[i][j] = max(dp1[i - 1][j], dp0[i - 1][j - 1] - prices[i])
第一個式子:當天沒有股票有兩種可能,要麼前一天就沒持有股票;要麼爲前一天持有股票,當天賣了。
第二個式子:當天持有股票有兩種可能,要麼前一天就持有股票,或者前一天沒持有股票,當天買入了。
注:這裏需要注意的是第二個式子中,第二種情況下的前一天的最大可交易次數爲j-1而不爲j,這是由於買入的次數要小於最大買入次數,假設爲dp0[i - 1][j] 他可能在i-1天之前已經進行過j次交易了,在第i天坑定不能再買入了。
傳遞函數有了,下面描述其的初值。
我們從傳遞函數發現,當前值總是與其正上方的值和左上角的值有關,因此只要對dp0和dp1的第一列第一行初始化即可。
dp0的第一列和第一行均爲0,首先第一列j=0,由於最大可交易次數爲0,那坑定不能買了爲0,第一行i= 0,由於在第1天,不持有股票只有一種可能就是沒有買所以爲0。
dp1的第一列,j= 0最大可交易次數爲0,但是還要持有,這是不可能實現的,因此爲非法狀態統統設爲負無窮;第一行(除第一個元素)i = 0,可交易次數大於0了,在第1天想持有股票,只能買第一天的,因此統統設爲 -1 * prices[0]。
實現代碼如下:
class Solution {
public int maxProfit(int k, int[] prices) {
if(k == 0 || prices.length <= 1){
return 0;
}
if(k > prices.length / 2){
return maxProfit(prices);
}
int[][] dp0 = new int[prices.length][k + 1];
int[][] dp1 = new int[prices.length][k + 1];
/* dp0[i][0] 和 dp0[0][j] 均爲0*/
for(int i = 0; i < prices.length; i++){
dp1[i][0] = Integer.MIN_VALUE;
}
for(int j = 1; j < k + 1; j++){
dp1[0][j] = -1 * prices[0];
}
for(int i = 1; i < prices.length; i++){
for(int j = 1; j < k + 1; j++){
dp0[i][j] = Math.max(dp0[i - 1][j], dp1[i - 1][j] + prices[i]);
dp1[i][j] = Math.max(dp1[i - 1][j], dp0[i - 1][j - 1] - prices[i]);
}
}
return dp0[prices.length - 1][k];
}
public int maxProfit(int[] prices) {
if(prices.length <= 1){
return 0;
}
int[] dp0 = new int[prices.length];
int[] dp1 = new int[prices.length];
dp1[0] = -1 * prices[0];
for(int i = 1; i < dp0.length; i++){
dp0[i] = Math.max(dp0[i - 1], dp1[i - 1] + prices[i]);
dp1[i] = Math.max(dp1[i - 1], dp0[i - 1] - prices[i]);
}
return dp0[dp0.length - 1];
}
}
由於k可能很大,當k > n / 2時問題就會轉化爲不用考慮最大交易次數的情況,可以認爲最大交易次數爲無窮。後面的代碼下一節將會分析。
問題二:最大可交易次數不限(leetcode122)
該問題是問題一中k = 無窮的特例,此時 k = k - 1,因此轉移函數變爲
dp0[i] = max(dp0[i - 1], dp1[i - 1] + prices[i])
dp1[i] = max(dp1[i - 1], dp0[i - 1] - prices[i])
問題由二維dp轉化爲一維dp
初值爲:d0[0] = 0,沒買肯定是0啊, d1[0] = -prices[0],買了肯定是- prices[0]。
實現代碼如下:
public int maxProfit(int[] prices) {
if(prices.length <= 1){
return 0;
}
int[] dp0 = new int[prices.length];
int[] dp1 = new int[prices.length];
dp1[0] = -1 * prices[0];
for(int i = 1; i < dp0.length; i++){
dp0[i] = Math.max(dp0[i - 1], dp1[i - 1] + prices[i]);
dp1[i] = Math.max(dp1[i - 1], dp0[i - 1] - prices[i]);
}
return dp0[dp0.length - 1];
}
問題三:最大交易次數爲1(leetcode121)
dp0[i][j] = max(dp0[i - 1][j], dp1[i - 1][j] + prices[i])
dp1[i][j] = max(dp1[i - 1][j], dp0[i - 1][j - 1] - prices[i])
將上式中的j換成1,得到如下:
dp0[i][1] = max(dp0[i - 1][1], dp1[i - 1][1] + prices[i])
dp1[i][1] = max(dp1[i - 1][1], dp0[i - 1][0] - prices[i])
由於dp0[i - 1][0] = 0,所以最終化簡後的轉移方程爲:
dp0[i]= max(dp0[i - 1], dp1[i - 1]+ prices[i])
dp1[i]= max(dp1[i - 1], 0 - prices[i])
初值:dp0[0] = 0, dp1[0] = -prices[0]
該結果也可以使用自然語言理解,當天不持有的有兩種情況,或者前一天就不持有,或者前一天持有當天賣出去了;當天持有的兩種情況爲:前一天就持有,或者前一天不會持有,當天買了。由於只能買一次,前一天不持有說明之前也沒進行過交易,因此前一天不持有的最大收益爲0.
實現代碼如下:
public int maxProfit(int[] prices) {
if(prices.length <= 1){
return 0;
}
int[] dp0 = new int[prices.length];// dp0[i] i天不持有股票的最大利潤
int[] dp1 = new int[prices.length]; // 持有股票的最大利潤
dp0[0] = 0;
dp1[0] = -1 * prices[0];
for(int i = 1; i < prices.length; i++){
dp0[i] = Math.max(dp0[i - 1], dp1[i - 1] + prices[i]);
dp1[i] = Math.max(dp1[i - 1], 0 - prices[i]);
}
return dp0[dp0.length - 1];
}
當然由於只交易一次,因此還可以很多其他辦法解決,比如可以使用貪心的策略,找到當前點之前最小的元素,即在前面那個位置買,在當前點賣。由於本文主要將dp,這裏就不多加贅述了。
問題四:最大交易次數爲2(leetcode123)
這問題沒啥說的,就令k=2咯,直接搞。
代碼如下:
public int maxProfit(int[] prices) {
if(prices.length <= 1){
return 0;
}
int K = 2;
int[][] dp0 = new int[prices.length][K + 1];
int[][] dp1 = new int[prices.length][K + 1];
// dp0[0][k] 和 dp0[i][0] 均爲0
for(int i = 0; i < prices.length; i++){ // 第一列 最大允許交易次數爲0 還要持有 定爲非法
dp1[i][0] = Integer.MIN_VALUE;
}
for(int k = 1; k <= K; k++){ //第一行
dp1[0][k] = -1 * prices[0];
}
for(int i = 1; i < prices.length; i++){
for(int k = 1; k <= K; k++){
dp0[i][k] = Math.max(dp0[i - 1][k], dp1[i - 1][k] + prices[i]);
dp1[i][k] = Math.max(dp1[i - 1][k], dp0[i - 1][k - 1] - prices[i]);
}
}
return dp0[prices.length - 1][K];
}
問題五:買賣股票最佳時間含手續費(leetcode714)
問題描述:
給定一個整數數組 prices,其中第 i 個元素代表了第 i 天的股票價格 ;非負整數 fee 代表了交易股票的手續費用。
你可以無限次地完成交易,但是你每筆交易都需要付手續費。如果你已經購買了一個股票,在賣出它之前你就不能再繼續購買股票了。
返回獲得利潤的最大值。
注意:這裏的一筆交易指買入持有並賣出股票的整個過程,每筆交易你只需要爲支付一次手續費。
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee
著作權歸領釦網絡所有。商業轉載請聯繫官方授權,非商業轉載請註明出處。
大體思路:
直接在k=無窮上改就行,由於每筆交易都含手續費,因此在賣的時候加上手續費就行,轉移方程如下:
dp0[i] = max(dp0[i - 1], dp1[i - 1] + prices[i] - fee)
dp1[i] = max(dp1[i - 1], dp0[i - 1] - prices[i])
初值 dp0[0] = 0, dp1[0] = - prices[0]
代碼如下:
public int maxProfit(int[] prices, int fee) {
if(prices.length <= 1){
return 0;
}
int[] dp0 = new int[prices.length];
int[] dp1 = new int[prices.length];
dp1[0] = -1 * prices[0];
for(int i = 1; i < prices.length; i++){
dp0[i] = Math.max(dp0[i - 1], dp1[i - 1] + prices[i] - fee);
dp1[i] = Math.max(dp1[i - 1], dp0[i - 1] - prices[i]);
}
return dp0[dp0.length - 1];
}
問題六:最佳股票買賣時機含冷凍期(leetcode309)
問題描述:
給定一個整數數組,其中第 i 個元素代表了第 i 天的股票價格 。
設計一個算法計算出最大利潤。在滿足以下約束條件下,你可以儘可能地完成更多的交易(多次買賣一支股票):
你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。
賣出股票後,你無法在第二天買入股票 (即冷凍期爲 1 天)。
示例:
輸入: [1,2,3,0,2]
輸出: 3
解釋: 對應的交易狀態爲: [買入, 賣出, 冷凍期, 買入, 賣出]
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown
著作權歸領釦網絡所有。商業轉載請聯繫官方授權,非商業轉載請註明出處。
大體思路:
由於當天賣出股票後第二天不能直接買入,也就是說只有前天沒持有,昨天沒持有,今天才可以進行買入。傳遞函數如下:
dp0[i] = max(dp0[i - 1], dp1[i - 1] + prices[i])
dp1[i] = max(dp1[i - 1], dp0[i - 2] - prices[i])
由於前天沒有持有,昨天也沒有持有,今天買入之前的收益是等於前天的收益的。因此爲dp0[i - 2] - prices[i]
由於用到i- 2,初值除了賦i= 0,還應對 i = 1賦值。
dp0[0] = 0;
dp1[0] = 0 - prices[0];
dp0[1] = Math.max(dp0[0], dp1[0] + prices[1]);
dp1[1] = Math.max(dp1[0], dp0[0] - prices[1]);// 由於-1時坑定沒賣出,因此爲dp0[0] - prices[1]。
實現代碼如下:
public int maxProfit(int[] prices) {
// return dfs(prices, 0, 0, 0, 0);
if(prices.length <= 1){
return 0;
}
int[] dp0 = new int[prices.length]; //dp0[i] 爲i天不持有股票的最大收益
int[] dp1 = new int[prices.length]; //
dp0[0] = 0;
dp1[0] = 0 - prices[0];
dp0[1] = Math.max(dp0[0], dp1[0] + prices[1]);
dp1[1] = Math.max(dp1[0], dp0[0] - prices[1]);
for(int i = 2; i < prices.length; i++){
dp0[i] = Math.max(dp0[i - 1], dp1[i - 1] + prices[i]);
dp1[i] = Math.max(dp1[i - 1], dp0[i - 2] - prices[i]);
}
return dp0[prices.length - 1];
}