给定一个整数数组 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
天的利润
在本题中,当天不持有股票的利润可以根据两种情况求得
- 如果前一天不持有股票,那么今天也不持有股票的利润就等于前一天不持有股票的利润
- 如果前一天持有股票,俺么今天不持有股票的利润就等于前一天持有股票的利润+当前的价格减去手续费
所以可以推导出状态转移方程如下:
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])
解题代码
基于以上基础,第一版代码如下:
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-买卖股票的最佳时机含手续费
如有任何问题或建议,欢迎留言讨论!