LeetCode 714. 买卖股票的最佳时机含手续费(中等)

题目描述

给定一个整数数组 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

示例 2:

输入: prices = [1,3,7,5,10,3], fee = 3
输出: 6

提示:

  • 1 <= prices.length <= 5 * 10^4
  • 0 <= prices[i], fee < 5 * 10^4

问题分析

这是一个典型的股票交易问题,与之前的股票问题相比,这道题的特殊之处在于每次交易都需要支付一定的手续费。我们需要考虑手续费对交易决策的影响,并设计算法以最大化总利润。

主要约束条件:

  1. 可以进行任意次数的交易(买入和卖出)
  2. 每次交易都需要支付固定的手续费
  3. 必须先买入才能卖出,且卖出后才能再次买入(同一时刻最多持有一股股票)

解题思路

动态规划方法

这类问题最适合使用动态规划来解决。我们可以定义两个状态来表示每一天结束时的情况:

  1. 持有股票状态:表示当天结束时手上持有股票的最大利润
  2. 不持有股票状态:表示当天结束时手上没有股票的最大利润
状态定义
  • hold[i]表示第 i 天结束时,持有一支股票的最大利润
  • cash[i]表示第 i 天结束时,没有持有股票的最大利润
状态转移方程

对于每一天,我们有以下决策:

  • 对于持有股票状态:
    • 要么是前一天就持有股票,今天继续持有:hold[i-1]
    • 要么是前一天没有股票,今天买入股票:cash[i-1] - prices[i]
   hold[i] = max(hold[i-1], cash[i-1] - prices[i])
  •  对于不持有股票状态:
    • 要么是前一天就没有股票,今天继续不持有:cash[i-1]
    • 要么是前一天持有股票,今天卖出(需要减去手续费):hold[i-1] + prices[i] - fee
   cash[i] = max(cash[i-1], hold[i-1] + prices[i] - fee)
初始条件
  • hold[0] = -prices[0]:第一天买入股票,利润为负的股票价格
  • cash[0] = 0:第一天不操作,利润为0
最终答案

最后一天不持有股票的状态就是最大利润,即 cash[n-1]。

空间优化

由于每一天的状态只依赖于前一天的状态,我们可以使用两个变量代替数组,将空间复杂度从 O(n) 优化到 O(1)。


算法执行过程

以示例1为例:prices = [1, 3, 2, 8, 4, 9], fee = 2

初始状态:

  • hold = -1(买入第一天的股票)
  • cash = 0(不操作)

第2天(价格=3):

  • prevCash = 0
  • cash = max(0, -1 + 3 - 2) = max(0, 0) = 0
  • hold = max(-1, 0 - 3) = max(-1, -3) = -1

第3天(价格=2):

  • prevCash = 0
  • cash = max(0, -1 + 2 - 2) = max(0, -1) = 0
  • hold = max(-1, 0 - 2) = max(-1, -2) = -1

第4天(价格=8):

  • prevCash = 0
  • cash = max(0, -1 + 8 - 2) = max(0, 5) = 5
  • hold = max(-1, 0 - 8) = max(-1, -8) = -1

第5天(价格=4):

  • prevCash = 5
  • cash = max(5, -1 + 4 - 2) = max(5, 1) = 5
  • hold = max(-1, 5 - 4) = max(-1, 1) = 1

第6天(价格=9):

  • prevCash = 5
  • cash = max(5, 1 + 9 - 2) = max(5, 8) = 8
  • hold = max(1, 5 - 9) = max(1, -4) = 1

最终结果:cash = 8


实例分析

让我们详细分析一下示例1的最优交易策略:

  • 第1天:价格为1,买入股票,支出1,当前资金为-1
  • 第4天:价格为8,卖出股票,获得8,支付手续费2,当前资金为5
  • 第5天:价格为4,买入股票,支出4,当前资金为1
  • 第6天:价格为9,卖出股票,获得9,支付手续费2,最终资金为8

这与我们的算法得出的最大利润8一致。


详细代码实现

Java 实现

class Solution {
    public int maxProfit(int[] prices, int fee) {
        // 边界检查
        if (prices == null || prices.length <= 1) {
            return 0;
        }
        
        int n = prices.length;
        
        // 定义状态变量
        // hold: 持有股票的最大利润
        // cash: 不持有股票的最大利润
        int hold = -prices[0];
        int cash = 0;
        
        // 遍历每一天
        for (int i = 1; i < n; i++) {
            // 临时保存前一天的cash值,因为更新hold时需要用到
            int prevCash = cash;
            
            // 更新cash(不持有股票的最大利润)
            // 1. 前一天不持有股票,今天继续不持有
            // 2. 前一天持有股票,今天卖出(减去手续费)
            cash = Math.max(cash, hold + prices[i] - fee);
            
            // 更新hold(持有股票的最大利润)
            // 1. 前一天持有股票,今天继续持有
            // 2. 前一天不持有股票,今天买入
            hold = Math.max(hold, prevCash - prices[i]);
        }
        
        // 最后一天不持有股票的状态即为最大利润
        return cash;
    }
}

C# 实现

public class Solution {
    public int MaxProfit(int[] prices, int fee) {
        // 边界检查
        if (prices == null || prices.Length <= 1) {
            return 0;
        }
        
        int n = prices.Length;
        
        // 定义状态变量
        // hold: 持有股票的最大利润
        // cash: 不持有股票的最大利润
        int hold = -prices[0];
        int cash = 0;
        
        // 遍历每一天,计算最大利润
        for (int i = 1; i < n; i++) {
            // 保存当前状态,因为更新hold时会用到原来的cash值
            int prevCash = cash;
            
            // 不持有股票的最大利润
            cash = Math.Max(cash, hold + prices[i] - fee);
            
            // 持有股票的最大利润
            hold = Math.Max(hold, prevCash - prices[i]);
        }
        
        // 最后一天不持有股票的状态即为最大利润
        return cash;
    }
}

复杂度分析

  • 时间复杂度:O(n),其中n是价格数组的长度,我们只需要遍历一次数组。
  • 空间复杂度:O(1),只使用了常数额外空间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值