Files
leetcode-master/problems/0714.买卖股票的最佳时机含手续费.md
2025-05-19 17:11:04 +08:00

362 lines
12 KiB
Markdown
Executable File
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

* [做项目多个C++、Java、Go、测开、前端项目](https://www.programmercarl.com/other/kstar.html)
* [刷算法(两个月高强度学算法)](https://www.programmercarl.com/xunlian/xunlianying.html)
* [背八股40天挑战高频面试题](https://www.programmercarl.com/xunlian/bagu.html)
# 714. 买卖股票的最佳时机含手续费
[力扣题目链接](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/)
给定一个整数数组 prices其中第 i 个元素代表了第 i 天的股票价格 非负整数 fee 代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
返回获得利润的最大值。
注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
示例 1:
* 输入: 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.
注意:
* 0 < prices.length <= 50000.
* 0 < prices[i] < 50000.
* 0 <= fee < 50000.
## 思路
本题优先掌握动态规划解法在动态规划章节中还会详细讲解本题
本题相对于[贪心算法122.买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II.html)多添加了一个条件就是手续费
### 贪心算法
[贪心算法122.买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II.html)中使用贪心策略不用关心具体什么时候买卖只要收集每天的正利润最后稳稳的就是最大利润了
而本题有了手续费就要关心什么时候买卖了因为计算所获得利润需要考虑买卖利润可能不足以扣减手续费的情况
如果使用贪心策略就是最低值买最高值如果算上手续费还盈利就卖
此时无非就是要找到两个点买入日期和卖出日期
* 买入日期其实很好想遇到更低点就记录一下
* 卖出日期这个就不好算了但也没有必要算出准确的卖出日期只要当前价格大于最低价格+手续费就可以收获利润至于准确的卖出日期就是连续收获利润区间里的最后一天并不需要计算是具体哪一天)。
所以我们在做收获利润操作的时候其实有三种情况
* 情况一收获利润的这一天并不是收获利润区间里的最后一天不是真正的卖出相当于持有股票所以后面要继续收获利润
* 情况二前一天是收获利润区间里的最后一天相当于真正的卖出了今天要重新记录最小价格了
* 情况三不作操作保持原有状态买入卖出不买不卖
贪心算法C++代码如下
```CPP
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int result = 0;
int minPrice = prices[0]; // 记录最低价格
for (int i = 1; i < prices.size(); i++) {
// 情况二:相当于买入
if (prices[i] < minPrice) minPrice = prices[i];
// 情况三:保持原有状态(因为此时买则不便宜,卖则亏本)
if (prices[i] >= minPrice && prices[i] <= minPrice + fee) {
continue;
}
// 计算利润,可能有多次计算利润,最后一次计算利润才是真正意义的卖出
if (prices[i] > minPrice + fee) {
result += prices[i] - minPrice - fee;
minPrice = prices[i] - fee; // 情况一,这一步很关键,避免重复扣手续费
}
}
return result;
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(1)
从代码中可以看出对情况一的操作因为如果还在收获利润的区间里表示并不是真正的卖出而计算利润每次都要减去手续费**所以要让minPrice = prices[i] - fee;这样在明天收获利润的时候才不会多减一次手续费**
大家也可以发现情况三那块代码是可以删掉的我是为了让代码表达清晰所以没有精简
### 动态规划
我在公众号代码随想录里将在下一个系列详细讲解动态规划所以本题解先给出我的C++代码带详细注释感兴趣的同学可以自己先学习一下
相对于[贪心算法122.买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II.html)的动态规划解法中只需要在计算卖出操作的时候减去手续费就可以了代码几乎是一样的
C++代码如下
```CPP
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
// dp[i][1]第i天持有的最多现金
// dp[i][0]第i天持有股票所剩的最多现金
int n = prices.size();
vector<vector<int>> dp(n, vector<int>(2, 0));
dp[0][0] -= prices[0]; // 持股票
for (int i = 1; i < n; i++) {
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee);
}
return max(dp[n - 1][0], dp[n - 1][1]);
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(n)
当然可以对空间进行优化因为当前状态只是依赖前一个状态
C++ 代码如下
```CPP
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int n = prices.size();
int holdStock = (-1) * prices[0]; // 持股票
int saleStock = 0; // 卖出股票
for (int i = 1; i < n; i++) {
int previousHoldStock = holdStock;
holdStock = max(holdStock, saleStock - prices[i]);
saleStock = max(saleStock, previousHoldStock + prices[i] - fee);
}
return saleStock;
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(1)
## 总结
本题贪心的思路其实是比较难的动态规划才是常规做法但也算是给大家拓展一下思路感受一下贪心的魅力
后期我们在讲解 股票问题系列的时候会用动规的方式把股票问题穿个线
## 其他语言版本
### Java
```java
// 贪心思路
class Solution {
public int maxProfit(int[] prices, int fee) {
int buy = prices[0] + fee;
int sum = 0;
for (int p : prices) {
if (p + fee < buy) {
buy = p + fee;
} else if (p > buy){
sum += p - buy;
buy = p;
}
}
return sum;
}
}
```
```java
class Solution { // 动态规划
public int maxProfit(int[] prices, int fee) {
if (prices == null || prices.length < 2) {
return 0;
}
int[][] dp = new int[prices.length][2];
// base case
dp[0][0] = 0;
dp[0][1] = -prices[0];
for (int i = 1; i < prices.length; i++) {
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee);
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
}
return dp[prices.length - 1][0];
}
}
```
### Python
```python
class Solution: # 贪心思路
def maxProfit(self, prices: List[int], fee: int) -> int:
result = 0
minPrice = prices[0]
for i in range(1, len(prices)):
if prices[i] < minPrice: # 此时有更低的价格,可以买入
minPrice = prices[i]
elif prices[i] > (minPrice + fee): # 此时有利润,同时假买入高价的股票,看看是否继续盈利
result += prices[i] - (minPrice + fee)
minPrice = prices[i] - fee
else: # minPrice<= prices[i] <= minPrice + fee 价格处于minPrice和minPrice+fee之间不做操作
continue
return result
```
### Go
```go
func maxProfit(prices []int, fee int) int {
var minBuy int = prices[0] //第一天买入
var res int
for i := 0; i < len(prices); i++ {
//如果当前价格小于最低价,则在此处买入
if prices[i] < minBuy {
minBuy = prices[i]
}
//如果以当前价格卖出亏本,则不卖,继续找下一个可卖点
if prices[i] >= minBuy && prices[i]-fee-minBuy <= 0 {
continue
}
//可以售卖了
if prices[i] > minBuy+fee {
//累加每天的收益
res += prices[i]-minBuy-fee
//更新最小值如果还在收获利润的区间里表示并不是真正的卖出而计算利润每次都要减去手续费所以要让minBuy = prices[i] - fee;,这样在明天收获利润的时候,才不会多减一次手续费!)
minBuy = prices[i]-fee
}
}
return res
}
```
### JavaScript
```Javascript
// 贪心思路
var maxProfit = function(prices, fee) {
let result = 0
let minPrice = prices[0]
for(let i = 1; i < prices.length; i++) {
if(prices[i] < minPrice) {
minPrice = prices[i]
}
if(prices[i] >= minPrice && prices[i] <= minPrice + fee) {
continue
}
if(prices[i] > minPrice + fee) {
result += prices[i] - minPrice - fee
// 买入和卖出只需要支付一次手续费
minPrice = prices[i] -fee
}
}
return result
};
// 动态规划
/**
* @param {number[]} prices
* @param {number} fee
* @return {number}
*/
var maxProfit = function(prices, fee) {
// 滚动数组
// have表示当天持有股票的最大收益
// notHave表示当天不持有股票的最大收益
// 把手续费算在买入价格中
let n = prices.length,
have = -prices[0]-fee, // 第0天持有股票的最大收益
notHave = 0; // 第0天不持有股票的最大收益
for (let i = 1; i < n; i++) {
// 第i天持有股票的最大收益由两种情况组成
// 1、第i-1天就已经持有股票第i天什么也没做
// 2、第i-1天不持有股票第i天刚买入
have = Math.max(have, notHave - prices[i] - fee);
// 第i天不持有股票的最大收益由两种情况组成
// 1、第i-1天就已经不持有股票第i天什么也没做
// 2、第i-1天持有股票第i天刚卖出
notHave = Math.max(notHave, have + prices[i]);
}
// 最后手中不持有股票,收益才能最大化
return notHave;
};
```
### TypeScript
> 贪心
```typescript
function maxProfit(prices: number[], fee: number): number {
if (prices.length === 0) return 0;
let minPrice: number = prices[0];
let profit: number = 0;
for (let i = 1, length = prices.length; i < length; i++) {
if (minPrice > prices[i]) {
minPrice = prices[i];
}
if (minPrice + fee < prices[i]) {
profit += prices[i] - minPrice - fee;
minPrice = prices[i] - fee;
}
}
return profit;
};
```
> 动态规划
```typescript
function maxProfit(prices: number[], fee: number): number {
/**
dp[i][1]: 第i天不持有股票的最大所剩现金
dp[i][0]: 第i天持有股票的最大所剩现金
*/
const length: number = prices.length;
const dp: number[][] = new Array(length).fill(0).map(_ => []);
dp[0][1] = 0;
dp[0][0] = -prices[0];
for (let i = 1, length = prices.length; i < length; i++) {
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee);
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
}
return Math.max(dp[length - 1][0], dp[length - 1][1]);
};
```
### Scala
贪心思路:
```scala
object Solution {
def maxProfit(prices: Array[Int], fee: Int): Int = {
var result = 0
var minPrice = prices(0)
for (i <- 1 until prices.length) {
if (prices(i) < minPrice) {
minPrice = prices(i) // 比当前最小值还小
}
if (prices(i) > minPrice + fee) {
result += prices(i) - minPrice - fee
minPrice = prices(i) - fee
}
}
result
}
}
```