線性DP (買賣股票系列)
線性dp 大盜阿福
我們先看一個線性dp的經典題型
題目連結
題目大意就是,一個劫匪要劫店,這裡的報警系統是相鄰兩家都被盜的時候才會報警,讓你判斷在不觸發報警的情況下取得的最大價值.
這道題dp做狀態轉移方程為:
f[i][0]這i家店不搶的狀態,它可以由前一個不搶的狀態來獲取
也可以從前一個點被搶來獲取;
f[i][1]這家店搶劫的狀態,它只能從前一個不搶的狀態加上價值來獲取
所以就有下面的方程
f[i][0]=max(f[i-1][0],f[i-1][1]);
f[i][1]=f[i-1][0]+w[i];
程式碼實現
#include<iostream>
#include<math.h>
#include<algorithm>
using namespace std;
const int maxn=1e5+5;
int f[maxn][2];
int w[maxn];
int main()
{
int n,m,t;
cin>>t;
while(t--)
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>w[i];
}
f[0][0]=0;
f[0][1]=0;
for(int i=1;i<=n;i++)
{
f[ i][0]=max(f[i-1][0],f[i-1][1]);
f[i][1]=f[i-1][0]+w[i];
}
cout<<max(f[n][1],f[n][0])<<endl;
}
}
121.買賣股票的最佳時機
題目連結
給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。
如果你最多只允許完成一筆交易(即買入和賣出一支股票一次),設計一個演算法來計算你所能獲取的最大利潤。
注意:你不能在買入股票前賣出股票。
示例一:
輸入: [7,1,5,3,6,4]
輸出: 5
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 5 天(股票價格 = 6)的時候賣出,最大利潤 = 6-1 = 5 。
注意利潤不能是 7-1 = 6, 因為賣出價格需要大於買入價格;同時,你不能在買入前賣出股票。
程式碼實現
通過一個最大值和最小值去更新遍歷,找到符合的最大值和最小值相減.
class Solution {
public:
int maxProfit(vector<int>& prices) {
if(prices.size()<=1)
return 0;
int minn=prices[0],maxx=0;
for(int i=1;i<prices.size();i++)
{
maxx=max(maxx,prices[i]-minn);
minn=min(minn,prices[i]);
}
return maxx;
}
};
122.買賣股票的最佳時機 二
題目連結
給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。
設計一個演算法來計算你所能獲取的最大利潤。你可以儘可能地完成更多的交易(多次買賣一支股票)。
注意:你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。
示例一:
輸入: [7,1,5,3,6,4]
輸出: 7
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 3 天(股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
隨後,在第 4 天(股票價格 = 3)的時候買入,在第 5 天(股票價格 = 6)的時候賣出, 這筆交易所能獲得利潤 = 6-3 = 3 。
程式碼實現
用dp陣列去遍歷,f[][0]是手中沒票,f[][1]是手中有票
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n=prices.size();
int f[n][2];
f[0][0]=0; //第一天沒票為0
f[0][1]=-prices[0];//第一天有票的情況,要買票
for(int i=1;i<=n-1;i++)
{
f[i][0]=max(f[i-1][0],f[i-1][1]+prices[i]);//從有票到沒票,要加上價值
f[i][1]=max(f[i-1][1],f[i-1][0]-prices[i]);//從沒票到有票,要減去價值
}
return f[n-1][0];//最後一天,沒票的情況最大,因為要賣票
}
};
買賣股票的最佳時機 三
題目連結
給定一個數組,它的第 i 個元素是一支給定的股票在第 i 天的價格。
設計一個演算法來計算你所能獲取的最大利潤。你最多可以完成 兩筆 交易。
注意: 你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。
程式碼實現
題目連結
這道題我們也用dp來實現用三維陣列來標記狀態
f[天數][第幾次交易][手中有無票的狀態];
我們用買入買票的時候來當做一次交易
第i天第j次交易無票狀態可以通過前一天的j次交易無票狀態轉移
也可以通過前一天的j次交易有票狀態賣票轉移
第i天第j次交易有票狀態可以通過前一天的j次交易有票狀態轉移
也可以通過前一天的j-1次交易無票狀態賣票轉移
所以狀態轉移方程就是
f[i][j][0]=max(f[i-1][j][0],f[i-1][j][1]+prices[i]);
//第i天第j次交易無票狀態可以通過前一天的j次交易無票狀態轉移
//也可以通過前一天的j次交易有票狀態賣票轉移
f[i][j][1]=max(f[i-1][j][1],f[i-1][j-1][0]-prices[i]);
//第i天第j次交易有票狀態可以通過前一天的j次交易有票狀態轉移
//也可以通過前一天的j-1次交易無票狀態賣票轉移
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n=prices.size();
int f[n][3][2];
memset(f,0,sizeof(f));
for(int i=0;i<=2;i++)
{
f[0][i][1]=-prices[0];
}
for(int i=1;i<=n-1;i++)
{
for(int j=1;j<=2;j++)
{
f[i][j][0]=max(f[i-1][j][0],f[i-1][j][1]+prices[i]);
f[i][j][1]=max(f[i-1][j][1],f[i-1][j-1][0]-prices[i]);
}
}
return f[n-1][2][0];
}
};
買賣股票的最佳時機 四
題目連結
給定一個整數陣列 prices ,它的第 i 個元素 prices[i] 是一支給定的股票在第 i 天的價格。
設計一個演算法來計算你所能獲取的最大利潤。你最多可以完成 k 筆交易。
注意:你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。
程式碼實現
上一道題的升級,就是把兩次交易變為k次交易
f[天數][第幾次交易][手中有無票的狀態];
我們用買入買票的時候來當做一次交易
第i天第j次交易無票狀態可以通過前一天的j次交易無票狀態轉移
也可以通過前一天的j次交易有票狀態賣票轉移
第i天第j次交易有票狀態可以通過前一天的j次交易有票狀態轉移
也可以通過前一天的j-1次交易無票狀態賣票轉移
所以狀態轉移方程就是
f[i][j][0]=max(f[i-1][j][0],f[i-1][j][1]+prices[i]);
//第i天第j次交易無票狀態可以通過前一天的j次交易無票狀態轉移
//也可以通過前一天的j次交易有票狀態賣票轉移
f[i][j][1]=max(f[i-1][j][1],f[i-1][j-1][0]-prices[i]);
//第i天第j次交易有票狀態可以通過前一天的j次交易有票狀態轉移
//也可以通過前一天的j-1次交易無票狀態賣票轉移
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
if(prices.size()<=1)
return 0;
int n=prices.size();
int f[n][k+1][2];
memset(f,0,sizeof(f));
for(int i=0;i<=k;i++)
{
f[0][i][1]=-prices[0];
}
for(int i=1;i<=n-1;i++)
{
for(int j=1;j<=k;j++)
{
f[i][j][0]=max(f[i-1][j][0],f[i-1][j][1]+prices[i]);
f[i][j][1]=max(f[i-1][j][1],f[i-1][j-1][0]-prices[i]);
}
}
return f[n-1][k][0];
}
};
其實上面的兩題可以用空間優化,從三維變為二維,原因是每次狀態其實都是從上一個緊挨著的天數來進行的更新的,所以每次更新都是挨著的,不用再填一個天數的狀態來多加一維空間
最佳買賣股票時機含冷凍期
題目連結
給定一個整數陣列,其中第 i 個元素代表了第 i 天的股票價格 。
設計一個演算法計算出最大利潤。在滿足以下約束條件下,你可以儘可能地完成更多的交易(多次買賣一支股票):
你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。
賣出股票後,你無法在第二天買入股票 (即冷凍期為 1 天)。
示例一:
輸入: [1,2,3,0,2]
輸出: 3
解釋: 對應的交易狀態為: [買入, 賣出, 冷凍期, 買入, 賣出]
程式碼實現
有三種狀態
f[i][0]為第i天賣掉票無票的第一天的狀態
f[i][1]為第i天有入票的狀態
f[i][2]為第i天為買票後的第二天後無票狀態
狀態轉移方程為
f[i][1]=max(f[i-1][1],f[i-1][2]-prices[i]);
f[i][0]=f[i-1][1]+prices[i];
f[i][2]=max(f[i-1][2],f[i-1][0]);
最後還要比較一下f[n-1][2],f[n-1][0]的大小.因為f[n-1][0]是最後一天賣票的狀態
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n=prices.size();
if(n<=1)
return 0;
int f[n][3];
memset(f,0,sizeof(f));
f[0][1]=-prices[0];
f[0][0]=-1e6;
f[0][2]=0;
for(int i=1;i<n;i++)
{
f[i][1]=max(f[i-1][1],f[i-1][2]-prices[i]);
f[i][0]=f[i-1][1]+prices[i];
f[i][2]=max(f[i-1][2],f[i-1][0]);
}
return max(f[n-1][2],f[n-1][0]);
}
};
買賣股票的最佳時機含手續費
給定一個整數陣列 prices,其中第 i 個元素代表了第 i 天的股票價格 ;非負整數 fee 代表了交易股票的手續費用。
你可以無限次地完成交易,但是你每筆交易都需要付手續費。如果你已經購買了一個股票,在賣出它之前你就不能再繼續購買股票了。
返回獲得利潤的最大值。
注意:這裡的一筆交易指買入持有並賣出股票的整個過程,每筆交易你只需要為支付一次手續費。
示例一:
輸入: prices = [1, 3, 2, 8, 4, 9], fee = 2
輸出: 8
解釋: 能夠達到的最大利潤:
在此處買入 prices[0] = 1
在此處賣出 prices[3] = 8
在此處買入 prices[4] = 4
在此處賣出 prices[5] = 9
總利潤: ((8 - 1) - 2) + ((9 - 4) - 2) = 8.
程式碼實現
這道題多加了一個手續費的問題,其實本質上和上面的題沒有什麼差別
就是在賣票的時候多減去一個數就行了。
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int n=prices.size();
if(n<=1)
return 0;
int f[n][2];
f[0][0]=0;
f[0][1]=-prices[0];
for(int i=1;i<n;i++)
{
f[i][0]=max(f[i-1][0],f[i-1][1]+prices[i]-fee);
f[i][1]=max(f[i-1][1],f[i-1][0]-prices[i]);
}
return f[n-1][0];
}
};