此文参考了labuladong的一方法团灭Leetcode股票买卖并根据自己的理解做出了一些修改,尤其是关于交易上限 k的描述,以及状态转移方程的解释
如果你希望直接看状态转移方程可以直接跳转状态转移方程
题目:
//给定一个整数数组 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
//。
//
//
//
// 提示:
//
//
// 0 <= k <= 100
// 0 <= prices.length <= 1000
// 0 <= prices[i] <= 1000
//
//
// Related Topics 数组 动态规划 👍 912 👎 0
package questions.leetcode.editor.cn
fun main() {
val solution = BestTimeToBuyAndSellStockIv.Solution()
val result = solution.maxProfit(2, intArrayOf(3, 2, 6, 5, 0, 3))
println(result)
}
class BestTimeToBuyAndSellStockIv {
// dp[d][0][0] = 0
// dp[d][0][1] = 不合法
// dp[d][k][2]
// dp[d][k][0] = max(dp[d-1][k][0], dp[d-1][k][1]+prices[d])
// dp[d][k][1] = max(dp[d-1][k][1], dp[d-1][k-1][0]-prices[d])
// dp[d][1][0] = max(dp[d-1][1][0], dp[d-1][0][1]+prices[d])
// = max(dp[d-1][1][0], 不合法)
// dp[d][1][1] = max(dp[d-1][1][1], dp[d-1][0][0]-prices[d])
// = max(dp[d-1][1][1], -prices[d])
// dp[0][k][0] = max(dp[-1][k][0], dp[-1][k-1][1]+prices[d])
// = 0
// dp[0][k][1] = max(dp[-1][k][1], dp[-1][k][0]-prices[d])
// = -prices[d]
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
fun maxProfit(kk: Int, prices: IntArray): Int {
val days = prices.size
if (days == 0) {
return 0
}
val dp = Array(days) {
Array(kk + 1) {
IntArray(2)
}
}
for (d in 0 until days) {
dp[d][0][0] = 0
dp[d][0][1] = Int.MIN_VALUE
}
for (d in 0 until days) {
for (k in 1..kk) {
if (d == 0) {
dp[d][k][0] = 0
dp[d][k][1] = -prices[d]
continue
}
dp[d][k][0] = maxOf(dp[d - 1][k][0], dp[d - 1][k][1] + prices[d])
dp[d][k][1] = maxOf(dp[d - 1][k][1], dp[d - 1][k - 1][0] - prices[d])
}
}
return dp[days - 1][kk][0]
}
}
//leetcode submit region end(Prohibit modification and deletion)
}
解析:
大家都知道动态规划要创建dp数组,dp数组的维度取决于状态的个数,状态的个数取决于操作(或者说选择),哪些操作会导致不同的结果
这个题目,天数、买入、卖出、买入卖出次数会影响最终的结果
天数:是伴随着股票涨跌的
买入卖出、买入卖出次数这个不必多说
题目给出,k为最大交易次数,也就是说 买入卖出次数最大为k,这个也是动态规划问题最难理解的
这里我们通常的做法是:将 k 所在的每种情况都遍历一遍
如果 k = 0,则不允许交易,则无论何时利润都是 0
如果 k = 1,则只允许一次交易
注意注意!!!接下来是关键!!!
如果 k = 2,那么 答案就只有 两种情况(最多交易 2 次,最多交易 1 次)
如果你对这个 k 感觉到疑惑,请继续往下看,我会在状态转移方程讲解一个关于时间线的点
如果 k = 3,那么 答案就只有以下种情况
- 3 次交易都被使用
- 只进行 2 次交易
- 只进行 1 次交易
- 不交易
所以我们只要 对比 以上情况的最大利润 就行了
而2,3 就是 k = 0,k = 1 的情况
此时假设我们已经给您算出来了交易一次的最大利润
那么,交易 2 次的最大利润 = 交易 1 次的最大利润 + 再进行 1 次交易
而题目是 不允许 同时进行多笔交易 的,所以我们要将 天数 这个变量计算进去
所以就变成了
交易 2 次的最大利润 = 第 i 天交易 1 次最大利润 + 第 j 天再进行 1 次交易
然后
交易 3 次的最大利润 = 第 ii 天交易 2 次最大利润 + 第 jj 天再进行 1 次交易
我们再添加上手持股票的状态,就变成了状态转移方程
状态转移方程
// 手里不持有股票的情况
dp[d][k][0] = maxOf(dp[d - 1][k][0], dp[d - 1][k][1] + prices[d])
// 手里持有股票的情况
dp[d][k][1] = maxOf(dp[d - 1][k][1], dp[d - 1][k - 1][0] - prices[d])
我规定,第一个参数是天数,第二个代表交易次数上限,第三个代表当前是否持有股票(0代表当前不持有股票,1代表持有股票)
举个例子:
dp[3][2][1] 的含义就是:在最多限制2次交易的情况下,第三天手里持有股票
也就是,在一个时间线里,k 也就是dp的第二个下标是不变的
而dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
之所以会有k-1,是因为k-1 是另一个时间线的,并不指的是k会随着天数不断下降
现在解释方程
// 手里不持有股票的情况
dp[d][k][0] = maxOf(dp[d - 1][k][0], dp[d - 1][k][1] + prices[d])
交易上限为 k 的时候,第 d 天,并且手里不持有股票的时候,这个时候,要么是昨天也这样,不买不卖,要么是昨天卖出了,所以prices[d]就是卖出所获,也就是昨天的股票价格
// 手里持有股票的情况
dp[d][k][1] = maxOf(dp[d - 1][k][1], dp[d - 1][k - 1][0] - prices[d])
交易上限为 k 的时候,第 d 天,并且手里持有股票,这个时候,要么是昨天也这样,手里持有股票,要么是昨天刚买的,
那么为什么有k-1呢???
因为dp[d][k][1]手里持有股票,所以要保证至少手里还剩1次,所以就等效了另一个时间线上的,当交易上限为k-1的时候,第d-1天,并且手里不持有股票的情况。
到这里,应该就可以完美解释你心中对 k 的疑惑了
其他的倒没什么问题
就是一些特殊情况
dp[d][0][0] = 0
dp[d][0][1] = 不合法 // k=0 的时候,不可能持有股票
dp[d][1][0] = max(dp[d-1][1][0], dp[d-1][0][1]+prices[d])
= max(dp[d-1][1][0], 不合法)
dp[d][1][1] = max(dp[d-1][1][1], dp[d-1][0][0]-prices[d])
= max(dp[d-1][1][1], -prices[d])
dp[0][k][0] = max(dp[-1][k][0], dp[-1][k-1][1]+prices[d])
= 0
dp[0][k][1] = max(dp[-1][k][1], dp[-1][k][0]-prices[d])
= -prices[d]
总结
动态规划 一定要多练,光看别人的思路是很难从根本上提高自己的