「这是我参与2022首次更文挑战的第34天,活动详情查看:2022首次更文挑战」
给定一个整数数组 prices ,它的第 **i 个元素 prices[i] 是一支给定的股票在第 i **天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。
注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入:k = 2, prices = [2,4,1]
输出:2
解释:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。
示例 2:
输入:k = 2, prices = [3,2,6,5,0,3]
输出:7
解释:在第 2 天 (股票价格 = 2) 的时候买入,在第 3 天 (股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6-2 = 4 。
随后,在第 5 天 (股票价格 = 0) 的时候买入,在第 6 天 (股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3-0 = 3 。
动态规划
与其余的股票问题类似,我们使用一系列变量存储「买入」的状态,再用一系列变量存储「卖出」的状态,通过动态规划的方法即可解决本题。
我们用 buy[i][j] 表示对于数组 prices[0..i] 中的价格而言,进行恰好 j 笔交易,并且当前手上持有一支股票,这种情况下的最大利润;用 sell[i][j] 表示恰好进行 j 笔交易,并且当前手上不持有股票,这种情况下的最大利润。
那么我们可以对状态转移方程进行推导。对于 buy[i][j],我们考虑当前手上持有的股票是否是在第 i 天买入的。如果是第 i 天买入的,那么在第 i−1 天时,我们手上不持有股票,对应状态 sell[i−1][j],并且需要扣除 prices[i] 的买入花费;如果不是第 i 天买入的,那么在第 i−1 天时,我们手上持有股票,对应状态 buy[i−1][j]。那么我们可以得到状态转移方程:
同理对于 sell[i][j],如果是第 i 天卖出的,那么在第 i−1 天时,我们手上持有股票,对应状态 buy[i−1][j−1],并且需要增加 prices[i] 的卖出收益;如果不是第 iii 天卖出的,那么在第 i−1 天时,我们手上不持有股票,对应状态 sell[i−1][j]。那么我们可以得到状态转移方程:
由于在所有的 n 天结束后,手上不持有股票对应的最大利润一定是严格由于手上持有股票对应的最大利润的,然而完成的交易数并不是越多越好(例如数组 prices 单调递减,我们不进行任何交易才是最优的),因此最终的答案即为 sell[n−1][0..k] 中的最大值。
var maxProfit = function(k, prices) {
if (!prices.length) {
return 0;
}
const n = prices.length;
k = Math.min(k, Math.floor(n / 2));
const buy = new Array(n).fill(0).map(() => new Array(k + 1).fill(0));
const sell = new Array(n).fill(0).map(() => new Array(k + 1).fill(0));
buy[0][0] = -prices[0];
sell[0][0] = 0;
for (let i = 1; i <= k; ++i) {
buy[0][i] = sell[0][i] = -Number.MAX_VALUE;
}
for (let i = 1; i < n; ++i) {
buy[i][0] = Math.max(buy[i - 1][0], sell[i - 1][0] - prices[i]);
for (let j = 1; j <= k; ++j) {
buy[i][j] = Math.max(buy[i - 1][j], sell[i - 1][j] - prices[i]);
sell[i][j] = Math.max(sell[i - 1][j], buy[i - 1][j - 1] + prices[i]);
}
}
return Math.max(...sell[n - 1]);
};