leetcode 力扣 188 股票交易(困难)

128 阅读4分钟

此文参考了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,那么 答案就只有以下种情况

  1. 3 次交易都被使用
  2. 只进行 2 次交易
  3. 只进行 1 次交易
  4. 不交易

所以我们只要 对比 以上情况的最大利润 就行了
而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]

总结

动态规划 一定要多练,光看别人的思路是很难从根本上提高自己的