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

338 lines
10 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.
## 算法公开课
**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html)[动态规划来决定最佳时机,这次含手续费!| LeetCode714.买卖股票的最佳时机含手续费](https://www.bilibili.com/video/BV1z44y1Z7UR)相信结合视频再看本篇题解更有助于大家对本题的理解**。
## 思路
本题贪心解法[贪心算法:买卖股票的最佳时机含手续费](https://programmercarl.com/0714.买卖股票的最佳时机含手续费.html)
性能是
* 时间复杂度O(n)
* 空间复杂度O(1)
本题使用贪心算法并不好理解也很容易出错那么我们再来看看使用动规的方法如何解题
相对于[动态规划122.买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II动态规划.html)本题只需要在计算卖出操作的时候减去手续费就可以了代码几乎是一样的
唯一差别在于递推公式部分所以本篇也就不按照动规五部曲详细讲解了主要讲解一下递推公式部分
这里重申一下dp数组的含义
dp[i][0] 表示第i天持有股票所得最多现金
dp[i][1] 表示第i天不持有股票所得最多现金
如果第i天持有股票即dp[i][0] 那么可以由两个状态推出来
* 第i-1天就持有股票那么就保持现状所得现金就是昨天持有股票的所得现金 dp[i - 1][0]
* 第i天买入股票所得现金就是昨天不持有股票的所得现金减去 今天的股票价格 dp[i - 1][1] - prices[i]
所以dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
在来看看如果第i天不持有股票即dp[i][1]的情况 依然可以由两个状态推出来
* 第i-1天就不持有股票那么就保持现状所得现金就是昨天不持有股票的所得现金 dp[i - 1][1]
* 第i天卖出股票所得现金就是按照今天股票价格卖出后所得现金**注意这里需要有手续费了**dp[i - 1][0] + prices[i] - fee
所以dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee);
**本题和[动态规划122.买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II动态规划.html)的区别就是这里需要多一个减去手续费的操作**
以上分析完毕C++代码如下
```CPP
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
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)
## 其他语言版本
### Java
```java
/**
* 卖出时支付手续费
* @param prices
* @param fee
* @return
*/
public int maxProfit(int[] prices, int fee) {
int len = prices.length;
// 0 : 持股(买入)
// 1 : 不持股(售出)
// dp 定义第i天持股/不持股 所得最多现金
int[][] dp = new int[len][2];
dp[0][0] = -prices[0];
for (int i = 1; i < len; i++) {
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
dp[i][1] = Math.max(dp[i - 1][0] + prices[i] - fee, dp[i - 1][1]);
}
return Math.max(dp[len - 1][0], dp[len - 1][1]);
}
/**
* 买入时支付手续费
* @param prices
* @param fee
* @return
*/
public int maxProfit(int[] prices, int fee) {
int len = prices.length;
// 0 : 持股(买入)
// 1 : 不持股(售出)
// dp 定义第i天持股/不持股 所得最多现金
int[][] dp = new int[len][2];
// 考虑买入的时候就支付手续费
dp[0][0] = -prices[0] - fee;
for (int i = 1; i < len; 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][0] + prices[i], dp[i - 1][1]);
}
return Math.max(dp[len - 1][0], dp[len - 1][1]);
}
// 一维数组优化
class Solution {
public int maxProfit(int[] prices, int fee) {
int[] dp = new int[2];
dp[0] = -prices[0];
dp[1] = 0;
for (int i = 1; i <= prices.length; i++) {
dp[0] = Math.max(dp[0], dp[1] - prices[i - 1]);
dp[1] = Math.max(dp[1], dp[0] + prices[i - 1] - fee);
}
return dp[1];
}
}
```Java
//使用 2*2 array
class Solution {
public int maxProfit(int[] prices, int fee) {
int dp[][] = new int[2][2];
int len = prices.length;
//[i][0] = holding the stock
//[i][1] = not holding the stock
dp[0][0] = -prices[0];
for(int i = 1; i < len; i++){
dp[i % 2][0] = Math.max(dp[(i - 1) % 2][0], dp[(i - 1) % 2][1] - prices[i]);
dp[i % 2][1] = Math.max(dp[(i - 1) % 2][1], dp[(i - 1) % 2][0] + prices[i] - fee);
}
return dp[(len - 1) % 2][1];
}
}
```
### python
```python
class Solution:
def maxProfit(self, prices: List[int], fee: int) -> int:
n = len(prices)
dp = [[0] * 2 for _ in range(n)]
dp[0][0] = -prices[0] #持股票
for i in range(1, n):
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[-1][0], dp[-1][1])
```
```python
class Solution:
def maxProfit(self, prices: List[int], fee: int) -> int:
# 持有股票手上的最大現金
hold = -prices[0] - fee
# 不持有股票手上的最大現金
not_hold = 0
for price in prices[1:]:
new_hold = max(hold, not_hold - price - fee)
new_not_hold = max(not_hold, hold + price)
hold, not_hold = new_hold, new_not_hold
return not_hold
```
### Go
```go
// 买卖股票的最佳时机含手续费 动态规划
// 时间复杂度O(n) 空间复杂度O(n)
func maxProfit(prices []int, fee int) int {
n := len(prices)
dp := make([][2]int, n)
dp[0][0] = -prices[0]
for i := 1; i < n; i++ {
dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i] - fee)
dp[i][0] = max(dp[i-1][0], dp[i-1][1] - prices[i])
}
return dp[n-1][1]
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
```
### JavaScript
```javascript
const maxProfit = (prices,fee) => {
let dp = Array.from(Array(prices.length), () => Array(2).fill(0));
dp[0][0] = 0 - prices[0];
for (let i = 1; i < prices.length; i++) {
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
dp[i][1] = Math.max(dp[i - 1][0] + prices[i] - fee, dp[i - 1][1]);
}
return Math.max(dp[prices.length - 1][0], dp[prices.length - 1][1]);
}
```
### TypeScript
```typescript
function maxProfit(prices: number[], fee: number): number {
/**
dp[i][0]:持有股票
dp[i][1]: 不持有
*/
const length: number = prices.length;
if (length === 0) return 0;
const dp: number[][] = new Array(length).fill(0).map(_ => []);
dp[0][0] = -prices[0];
dp[0][1] = 0;
for (let i = 1; i < length; i++) {
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee);
}
return dp[length - 1][1];
};
```
### C:
```c
#define max(a, b) ((a) > (b) ? (a) : (b))
// dp[i][0] 表示第i天持有股票所省最多现金。
// dp[i][1] 表示第i天不持有股票所得最多现金
int maxProfit(int* prices, int pricesSize, int fee) {
int dp[pricesSize][2];
dp[0][0] = -prices[0];
dp[0][1] = 0;
for (int i = 1; i < pricesSize; ++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 dp[pricesSize - 1][1];
}
```
### Rust:
**贪心**
```Rust
impl Solution {
pub fn max_profit(prices: Vec<i32>, fee: i32) -> i32 {
let mut result = 0;
let mut min_price = prices[0];
for i in 1..prices.len() {
if prices[i] < min_price { min_price = prices[i]; }
// if prices[i] >= min_price && prices[i] <= min_price + fee { continue; }
if prices[i] > min_price + fee {
result += prices[i] - min_price - fee;
min_price = prices[i] - fee;
}
}
result
}
}
```
**动态规划**
```Rust
impl Solution {
pub fn max_profit(prices: Vec<i32>, fee: i32) -> i32 {
let mut dp = vec![vec![0; 2]; prices.len()];
dp[0][0] = -prices[0];
for (i, &p) in prices.iter().enumerate().skip(1) {
dp[i][0] = dp[i - 1][0].max(dp[i - 1][1] - p);
dp[i][1] = dp[i - 1][1].max(dp[i - 1][0] + p - fee);
}
dp[prices.len() - 1][1]
}
}
```
**动态规划空间优化**
```rust
impl Solution {
pub fn max_profit(prices: Vec<i32>, fee: i32) -> i32 {
let (mut low, mut res) = (-prices[0], 0);
for p in prices {
low = low.max(res - p);
res = res.max(p + low - fee);
}
res
}
}
```