714 买卖股票的最佳时机含手续费-动态规划(中等)
给定一个整数数组 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.
思路:
- 动态规划
- 贪心算法
1、C++动态规划
定义状态:
首先来定义状态。一般问题要求什么,dp的值就定义成什么,那么dp[i]应该表示第i天的最大利润。但仅有天数的信息我们还无法推出状态转移方程,说明信息还不够。这时再分析题目:根据题意,当手上持有股票的时候就不能再继续买入。可见每天的状态无非就两种:持有股票和不持有股票。到这里dp的定义就出来了:dp[i][0]表示第i天交易完后手里没有股票的最大利润,dp[i][1]表示第i天交易完后手里持有股票的最大利润。
状态转移方程:
每天的动作总共有三种:买、卖、不动,而每一天的最大利润都可以由前一天的最大利润转移而来,综上不难得出:
d p [ i ] [ 0 ] = m a x ( d p [ i − 1 ] [ 0 ] , d p [ i − 1 ] [ 1 ] + p r i c e s [ i ] − f e e ) dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee) dp[i][0]=max(dp[i−1][0],dp[i−1][1]+prices[i]−fee)
d p [ i ] [ 1 ] = m a x ( d p [ i − 1 ] [ 1 ] , d p [ i − 1 ] [ 0 ] − p r i c e s [ i ] ) dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]) dp[i][1]=max(dp[i−1][1],dp[i−1][0]−prices[i])。
注意一开始dp的初始化。每次对dp数组进行初始化的要点就是根据“定义”去解释一开始的边界的含义。对于本题,$dp[0][0]=0,dp[0][1]=-price[0] $。
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int n = prices.size();
int d[n][2];
d[0][0]=0;
d[0][1]=-(prices[0]);
for(int i =1;i<n;i++){
d[i][0] = max( d[i-1][0], (d[i-1][1]+prices[i]-fee));
d[i][1] = max( d[i-1][1], (d[i-1][0]-prices[i]));
}
return d[n-1][0];
}
};
2、python-动态规划
由于第i天的状态只与第i-1天的状态有关,因此可以将d数组进行空间上的压缩,压缩为1维。d[0]\d[1]。
用buy表示d[1],sell表示d[0]。代码如下:
import numpy as np
class Solution:
def maxProfit(self, prices: List[int], fee: int) -> int:
n = len(prices)
buy = -prices[0]
sell = 0
for i in range(1,n):
new_buy = max(buy , sell-prices[i])
new_sell = max(sell, buy+prices[i]-fee)
buy = new_buy
sell = new_sell
return sell
3、C+±贪心
思路:(在最低价时买入、最高价时卖出,依次操作)
把手续费转移到买入时支付(只要是在一次买卖当中支付手续费即可),我们就可以通过“尽可能最小化买入成本,最大化卖出价格”的贪心策略求得最大利润了。
设buy为在最大收益前提下,如果我们手上拥有一支股票,那么它的最低买入成本是多少。
所以我们在做收获利润操作的时候其实有三种情况:
- 情况一: p r i c e [ i ] > b u y price[i]>buy price[i]>buy:此时可以卖出并计算利润,利润为 p r i c e [ i ] − b u y price[i]-buy price[i]−buy。然而由于此时并不是收获利润区间里的最后一天(不是真正的卖出,相当于持有股票),所以后面要继续收获利润。此时需要将buy置为 p r i c e [ i ] price[i] price[i]。相当于再卖出获得利润的同时又立即以相同的价格且不加手续费的获得了一只股票。当后续价格仍增长时, p r i c e [ i + 1 ] − p r i c e [ i ] + ( p r i c e [ i ] − b u y ) = p r i c e [ i + 1 ] − b u y price[i+1]-price[i]+(price[i]-buy)=price[i+1]-buy price[i+1]−price[i]+(price[i]−buy)=price[i+1]−buy.
- 情况二: p r i c e [ i ] + f e e < b u y price[i]+fee < buy price[i]+fee<buy:此时买入价格更低,应重新记录买入价格。(前一天是收获利润区间里的最后一天(相当于真正的卖出了),今天要重新记录最小价格了。)
- 情况三:KaTeX parse error: Expected '}', got '&' at position 32: …uy \text{ &̲& } buy>=…不作操作,保持原有状态(买入,卖出,不买不卖)
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int n = prices.size();
int buy = prices[0]+fee;
int res = 0;
for(int i=1;i<n;i++){
if(prices[i]+fee < buy){
buy = prices[i]+fee;
}
if(prices[i] > buy){
res += prices[i]-buy;
buy = prices[i];
}
}
return res;
}
};
4、python–贪心
手续费在卖出时计算。
import numpy as np
class Solution:
def maxProfit(self, prices: List[int], fee: int) -> int:
n = len(prices)
buy = prices[0]
res = 0
for i in range(1,n):
if(prices[i]<buy):
buy = prices[i]
if(prices[i]-fee > buy):
res += prices[i]-fee-buy
buy = prices[i]-fee
return res