题目I:买卖一次
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。
注意:你不能在买入股票前卖出股票。
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
分析
方法1
动态规划
class Solution {
public:
int maxProfit(vector<int>& prices) {
if(prices.size()<=1) return 0;
//使用前缀和数组,每次都找自己左边的最小值
int res = 0;
int max_v = *max_element(prices.begin(), prices.end());
vector<int> dp(prices.size(), max_v+1);
if(prices[0]<prices[1]) dp[1] = prices[0];
for(int i=2;i<prices.size();i++){
if(prices[i-1]<dp[i-1]) dp[i] = prices[i-1];
else dp[i] = dp[i-1];
}
for(int i=0;i<dp.size();i++){
res = max(res, prices[i]-dp[i]);
}
return res;
}
};
方法2
改进空间的动态规划。
由于当前状态dp[i]只与前一个状态相关,因此可以不用使用数组将所有状态进行存储,直接以一个变量来维护即可。
class Solution {
public:
int maxProfit(vector<int>& prices) {
if(prices.size()<=1) return 0;
int min_v = prices[0];//维护一个股票的最小值
int res = 0;
for(int i=0;i<prices.size();i++){
res = max(res, prices[i]-min_v);
min_v = min(min_v, prices[i]);
}
return res;
}
};
题目II:买卖多次
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
分析
关键点在于同一支股票可以买卖多次。
本题可以直接使用贪心。[7, 1, 5, 6] 第二天买入,第四天卖出,收益最大(6-1),所以怎么判断不是第三天就卖出了呢? 这里就把问题复杂化了,根据题目的意思,当天卖出以后,当天还可以买入,所以其实可以第三天卖出,第三天买入,第四天又卖出((5-1)+ (6-5) === 6 - 1)。所以算法可以直接简化为只要今天比昨天大,就卖出。
class Solution {
public:
int maxProfit(vector<int>& prices) {
//使用贪心思想
int res = 0;
for(int i=1;i<prices.size();i++){
if(prices[i]>prices[i-1]) res += prices[i]-prices[i-1];
}
return res;
}
};
题目III:买卖两次
分析
本题可以是第一题的改进,在第一题的基础上进行扫描两次。设状态f(i),表示区间 [ 0 , i ] [0, i] [0,i] ( 0 < = i < = n − 1 ) (0<=i<=n-1) (0<=i<=n−1)的最大利润,状态g(i),表示区间区间 [ i , n − 1 ] [i, n-1] [i,n−1] ( 0 < = i < = n − 1 ) (0<=i<=n-1) (0<=i<=n−1)的最大利润,则最终答案为max (f(i) + g(i)) , ( 0 < = i < = n − 1 ) (0<=i<=n-1) (0<=i<=n−1)。
class Solution {
public:
int maxProfit(vector<int>& prices) {
if(prices.size()<=1) return 0;
int min_v = prices[0];//维护一个股票的最小值
int res = 0;
vector<int> f(prices.size(), 0);
vector<int> g(prices.size(), 0);
for(int i=0;i<prices.size();i++){
res = max(res, prices[i]-min_v);
f[i] = res;
min_v = min(min_v, prices[i]);
}
res = 0;
int max_v = prices[prices.size()-1]; //从右往左的话,则维护一个股票的最大值
for(int i=prices.size()-2;i>=0;i--){
res = max(res, max_v - prices[i]);
g[i] = res;
max_v = max(max_v, prices[i]);
}
int ans = 0;
for(int i=0;i<prices.size();i++) ans = max(ans, f[i]+g[i]);
return ans;
}
};
题目IV:至多买卖k次
分析
本题是对上情况的泛化。主要采用三维dp, 定义状态dp[i][k][j]其中j可以取0和1。
- dp[i][k][1]表示前i天,最多交易k次,且当前持有股票时所获得的最大利润
- dp[i][k][0]表示前i天,最多交易k次,且当前不持有股票时所获得的最大利润
- 注意一次交易指的是买和卖这两个合起的动作
class Solution {
public:
int maxProfit(int max_k, vector<int>& prices) {
int n = prices.size();
//当k大于n/时就表示交易的次数无数次
if(max_k>n/2) return maxMultiProfit(prices);
//主要采用三维dp, 定义状态dp[i][k][j]其中j可以取0和1
//dp[i][k][1]表示前i天,最多交易k次,且当前持有股票时所获得的最大利润
//dp[i][k][0]表示前i天,最多交易k次,且当前不持有股票时所获得的最大利润
//注意一次交易指的是买和卖这两个合起的动作
vector<vector<vector<int>>> dp(n, vector<vector<int>>(max_k+1, vector<int>(2, 0)));
for(int i=0;i<prices.size();i++){
//处理base case
for(int k=max_k;k>=1;k--){
if((i-1)==-1){//即i=0
//当第一维为-1的时候
dp[i][k][0] = 0;
dp[i][k][1] = -prices[i];
}
else{
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1]+prices[i]);//dp[i-1][k][0]表示i的上一天也没有股票也没有交易,即休息,dp[i-1][k-1][1]表示i的上一天有股票, 并交易卖了股票
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0]-prices[i]);//第一个是不交易,第二个是买入了股票
}
}
}
return dp[n-1][max_k][0];
}
//可以多次买卖同一支股票
int maxMultiProfit(vector<int>& prices) {
//使用贪心思想
int res = 0;
for(int i=1;i<prices.size();i++){
if(prices[i]>prices[i-1]) res += prices[i]-prices[i-1];
}
return res;
}
};