[LeetCode] Best Time to Buy and Sell Stock 6道合集【DP】

时间:2023-02-01 08:49:40

1. Best Time to Buy and Sell Stock

2. Best Time to Buy and Sell Stock II

3. Best Time to Buy and Sell Stock III

4. Best Time to Buy and Sell Stock IV

5. Best Time to Buy and Sell Stock with Cooldown

6. Best Time to Buy and Sell Stock with Transaction Fee

一个一个来:

1. Best Time to Buy and Sell Stock

题意:给定股票每日的售价,求 一次买进和一次卖出最多可以获取多大利润?

思路:其实就是求买进时和卖出是最大差价。每次更新当前最小价格,dp即可。

 class Solution {
public:
int maxProfit(vector<int> &prices) {
int maxPro = ;
int minPrice = INT_MAX;
for(int i = ; i < prices.size(); i++){
minPrice = min(minPrice, prices[i]);
maxPro = max(maxPro, prices[i] - minPrice);
}
return maxPro;
}
};

2. Best Time to Buy and Sell Stock II

题意:给定股票每日的售价,求若干次买卖最多可以获取多大利润?

思路:其实就是求股票低价买,高价卖最多能获得多少钱。求连续上升的高度和。

如12314为 1买进3卖出1买进4卖出。

 class Solution {
public:
int maxProfit(vector<int>& prices) {
if(prices.size()==)return ;
int ans=;
for(int i=;i<prices.size()-;i++)
if(prices[i+]>prices[i]) ans+=prices[i+]-prices[i];
return ans;
}
};

3. Best Time to Buy and Sell Stock III

题意:给定股票每日的售价,求最多两次买卖最多可以获取多大利润?

思路:DP

设dp[i][j]为到第j天第i次交易能获取的最大值。当前状态由前一时间没有交易转移而来,或者由i-1次交易j之前所有时间点买入,当前的时间点卖出完成一次交易转移

转移方程:dp[i, j] = max(dp[i, j-1], prices[i] - prices[k] + dp[i-1, k-1]), k=[0..j-1]  k的循环可以简化

Time complexity is O(kn), space complexity is O(kn).

 class Solution {
public:
int maxProfit(vector<int> & prices)
{
if(prices.size()<=)
return ;
else{
int K=;
vector<vector<int>> dp(K+,vector<int>(prices.size(),));
for(int i=;i<=K;i++)
{
int tmpMax = -prices[];
for(int j=;j<prices.size();j++)
{
tmpMax=max(tmpMax,dp[i-][j-]-prices[j-]); // k那一步的循环简化,注意这里实际是算的dp[i-1][j-2], 因为dp[i-1][j-1]同时买卖相当于没交易
dp[i][j]=max(dp[i][j-],prices[j]+tmpMax);
}
}
return dp[][prices.size()-];
}
}
};

4. Best Time to Buy and Sell Stock IV

题意:给定股票每日的售价,求最多k次买卖最多可以获取多大利润?

思路:同III的2次交易,换成K后直接做可能超时,考虑当k最多为n/2, 当k>=n/2时相当于无穷次,即Best Time to Buy and Sell Stock II

 class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
if(prices.size()<=)
return ;
else{
int K=k,maxProf=;
if (k >= prices.size() / ) return quickSolve(prices);
vector<vector<int>> dp(K+,vector<int>(prices.size(),));
for(int i=;i<=K;i++)
{
int tmpMax=-prices[];
for(int j=;j<prices.size();j++)
{
tmpMax=max(tmpMax,dp[i-][j-]-prices[j-]);
dp[i][j]=max(dp[i][j-],prices[j]+tmpMax);
maxProf=max(maxProf,dp[i][j]);
}
}
return maxProf;
}
}
int quickSolve(vector<int>& prices)
{
int ans=;
for(int i=;i<prices.size()-;i++)
{
if(prices[i]<prices[i+])ans+=prices[i+]-prices[i];
}
return ans;
}
};

5. Best Time to Buy and Sell Stock with Cooldown

题意:给定一个数组prices,prices[i]代表第i天股票的价格。让你进行若干次买卖,求最大利润

  • 你每次只能买一支而且在再次买入之前必须出售之前手头上的股票(就是手头上最多有一支股票)
  • 每次出售需要休息一天才能再次买入

思路:对于某一天的最大收益,分两种情况,一种是这天存了一支股票,另一种是手里没有股票,双状态DP

sell[i] 卖出操作的最大利润。它需要考虑的是,第i天是否卖出。(手上有stock在第i天所能获得的最大利润)

buy[i] 买进操作的最大利润。它需要考虑的是,第i天是否买进。(手上没有stock在第i天所能获得的最大利润)

所以,有状态转移方程

  • buy[i] = max(buy[i-1] , sell[i-2] – prices[i])  // 休息一天再买入,所以是sell[i-2]在状态转移
  • sell[i] = max(sell[i-1], buy[i-1] + prices[i])

最后显然有sell[n-1] > buy[n-1] 所以我们返回sell[n-1]

 class Solution {
public:
int maxProfit(vector<int>& prices) {
if (prices.size() < ) return ;
vector<int> buy(prices.size(), ), sell(prices.size(), );
buy[] = -prices[];
buy[] = max(-prices[], -prices[]); //注意开始手里没有stock
sell[] = max(, buy[] + prices[]);
for (int i = ; i < prices.size(); i++) {
sell[i] = max(sell[i - ], buy[i - ] + prices[i]);
buy[i] = max(buy[i - ], sell[i - ] - prices[i]);
}
return sell[prices.size() - ];
}
};

精简版:

prev_sell用来存储sell[i-2],由于buy的值要么取buy[i-1],要么为prev_sell-price[i],当为prev_sell-price[i]时,sell一定取前一次的sell,所以不影响sell的新值,因此prev_buy不必要。

 class Solution {
public:
int maxProfit(vector<int> &prices) {
int buy(INT_MIN), sell(), prev_sell(), prev_buy;
for (int price : prices) {
prev_buy = buy; // not necessary
buy = max(prev_sell - price, buy);
prev_sell = sell;
sell = max(prev_buy + price, sell);
}
return sell;
}
};

6. Best Time to Buy and Sell Stock with Transaction Fee

题目大意:给定股票的价格,以及每次交易需要的花费fee,求能获得的最大利润

思路:双状态DP,类似于Best Time to Buy and Sell Stock with Cooldown

状态的定义:对于第i天的最大收益,应分成两种情况,一是该天结束后手里没有stock,可能是保持前一天的状态也可能是今天卖出了,此时令收益为sold;二是该天结束后手中有一个stock,可能是保持前一天的状态,也可能是今天买入了,用hold表示。由于第i天的情况只和i-1天有关,所以用两个变量sold和hold就可以,不需要用数组。

sold 考虑到第i天为止卖出后的收益,hold考虑到第i天为止买入持有收益

则有:

  • sold[i] = max(sold[i-1], hold[i-1] + prices[i] – fee)
  • hold[i] = max(hold[i-1], sold[i-1] – prices[i])

需要注意sold和hold的初始值,最终输出sold,因为最后一天的情况一定是手里没有stock的

 class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
vector<int> sold(prices.size(),), hold=sold;
hold[]=-prices[];
sold[]=;
for(int i=;i<prices.size();i++)
{
sold[i]=max(sold[i-],hold[i-]+prices[i]-fee);
hold[i]=max(hold[i-],sold[i-]-prices[i]);
}
return sold.back();
}
};
 class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int cash=;//the maxPro you have if you don't have a stock that day
int hold=-prices[];//the maxPro you have if you have a stock that day, if you have a stock the first day,hold=-prices[0]
int i;
for(i=;i<prices.size();i++){
cash=max(cash,hold+prices[i]-fee);//cash in day i is the maxvalue of cash in day i-1 or you sell your stock
hold=max(hold,cash-prices[i]); // 如果买卖不同时进行,不记录过去也可以,如果买卖同时进行,没有fee的II题相当于没有交易,这题有fee则同一天买卖会亏。因此存prev_cash和直接用today’s cash等价
}
return cash;
}

本题可以贪心求:

贪心选择的关键是找到一个最大后是不是能够卖掉stock,重新开始寻找买入机会。比如序列1 3 2 8,如果发现2小于3就完成交易买1卖3,此时由于fee=2,(3-1-fee)+(8-2-fee)<(8-1-fee),所以说明卖早了,令max是当前最大price,当(max-price[i]>=fee)时可以在max处卖出,且不会存在卖早的情况,再从i开始重新寻找买入机会

 class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int profit=;
int curProfit=;
int minP=prices[];
int maxP=prices[];
int i;
for(i=;i<prices.size();i++){
minP=min(minP,prices[i]);
maxP=max(maxP,prices[i]);
curProfit=max(curProfit,prices[i]-minP-fee);
if((maxP-prices[i])>=fee){//can just sell the stock at maxP day.
profit+=curProfit;
curProfit=;
minP=prices[i];
maxP=prices[i];
}
}
return profit+curProfit;//the last trade have to be made if there is some profit
}

curProfit记录了当前一次交易能得到的最大收益,只有当maxP-prices[i]>=fee时,才将curProfit累加到总的收益中。最后一次交易不需要考虑是否早卖了,所以直接累加最后一次的curProfit