[路飞]_leetcode-714-买卖股票的最佳时机含手续费

415 阅读4分钟

[题目地址] [B站地址]

给定一个整数数组 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 * 104
  • 1 <= prices[i] < 5 * 104
  • 0 <= fee < 5 * 104

本题要求得可以获得的最大利润。涉及到求最优解的问题,可以用动态规划来做

状态定义

接下来我们思考下,本题中的利润和什么有关呢?

答案是天数以及当天是否持有股票

所以这里的状态定义应该是一个二维 dp

dp[i][0] 表示第 i 天不持有股票的利润 dp[i][1] 表示第 i 天持有股票的利润

转移方程

有了状态定义后,我们需要思考的就是如果求得第 i 天的利润

在本题中,当天不持有股票的利润可以根据两种情况求得

  1. 如果前一天不持有股票,那么今天也不持有股票的利润就等于前一天不持有股票的利润
  2. 如果前一天持有股票,俺么今天不持有股票的利润就等于前一天持有股票的利润+当前的价格减去手续费 所以可以推导出状态转移方程如下:

dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i]-fee)

当天持有股票的利润同样可以根据两种情况求得

  1. 如果前一天不持有股票,那么今天持有股票的利润就等于前一天不持有股票的利润-今天的价格
  2. 如果前一天持有股票,那么今天持有股票的利润就等于前一天持有股票的利润 所以可以推导状态转移方程如下:

dp[i][1] = Math.max(dp[i-1][0]-prices[i],dp[i-1][1])

解题代码

基于以上基础,第一版代码如下:

var maxProfit = function(prices, fee) {
  const dp = [[0,-prices[0]]];
  // 求得每一天持有股票和不持有股票的利润
  for(let i = 1;i<prices.length;i++){
    dp[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]
};

这里要注意的是通常理解,手续费是本次交易金额的一部分,所以最后肯定是把手里的股票卖出利润更高,返回最后一天不持有股票的利润即可。

以上代码提交是可以通过的,但是内存消耗只击败了 40+% 的用户

这里空间复杂度高是因为要在dp数组中创建prices.length个子数组,空间复杂度为 O(2n) = O(n)

本题第 i 天的利润只依赖 i-1 天的利润,所以可以利用滚动数组的技巧来优化空间复杂度

代码如下:

var maxProfit = function(prices, fee) {
  const dp = [[0,-prices[0]],[]];
  // 求得每一天持有股票和不持有股票的利润
  for(let i = 1;i<prices.length;i++){
    const cur = i%2,
    pre = !cur*1;
    dp[cur][0] = Math.max(dp[pre][0],dp[pre][1]+prices[i]-fee)
    dp[cur][1] = Math.max(dp[pre][1],dp[pre][0]-prices[i])
  }
  // 返回最后一天不持有股票的利润
  return dp[(prices.length-1)%2][0]
};

这里滚动数组的技巧是利用下标 i%2 的结果不停的更新 dp 数组下标 0、1的数据,达到存储第 i-1 和第 i 天数据的目的

至此我们就完成了 leetcode-714-买卖股票的最佳时机含手续费

如有任何问题或建议,欢迎留言讨论!