算法---LeetCode 188. 买卖股票的最佳时机 IV(股票买卖问题总结)

1. 题目

原题链接

给定一个整数数组 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 数组 动态规划
👍 530 👎 0

2. 题解

2.1 解法1: 动态规划

1.状态定义

对于股票交易问题, 动态规划的思想主要是 穷举状态, 然后再找出每个 状态 对应的 选择 , 对于该题, 问题的 状态 有三个, 第一个是天数,第二个是 允许交易的最大次数,第三个是 当前的股票持有状态; 所以有以下状态定义:

dp[i][k][0 or 1]
0 <= i <= n-1, 1 <= k <= K
n 为天数,大 K 为最多交易数
此问题共 n × K × 2 种状态,全部穷举就能搞定。

for 0 <= i < n:
    for 1 <= k <= K:
        for s in {0, 1}:
            dp[i][k][s] = max(buy, sell, rest)
2.状态转移方程
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
              max(   选择 rest  ,             选择 sell      )

解释:今天我没有持有股票,有两种可能:
要么是我昨天就没有持有,然后今天选择 rest,所以我今天还是没有持有;
要么是我昨天持有股票,但是今天我 sell 了,所以我今天没有持有股票了。

dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
              max(   选择 rest  ,           选择 buy         )

解释:今天我持有着股票,有两种可能:
要么我昨天就持有着股票,然后今天选择 rest,所以我今天还持有着股票;
要么我昨天本没有持有,但今天我选择 buy,所以今天我就持有股票了。

注意 k 的限制,我们在选择 buy 的时候,把 k 减小了 1,当然你也可以在 sell 的时候减 1, 两者不重复即可

3.状态初始化(定义 base case)
dp[-1][k][0] = 0
解释:因为 i 是从 0 开始的,所以 i = -1 意味着还没有开始,这时候的利润当然是 0 。
dp[-1][k][1] = -infinity
解释:还没开始的时候,是不可能持有股票的,用负无穷表示这种不可能。
dp[i][0][0] = 0
解释:因为 k 是从 1 开始的,所以 k = 0 意味着根本不允许交易,这时候利润当然是 0 。
dp[i][0][1] = -infinity
解释:不允许交易的情况下,是不可能持有股票的,用负无穷表示这种不可能。

对于状态的初始化, 可根据具体问题, 来具体的考虑

4.具体题目解答
1. 本题 188.买卖股票的最佳时机 IV(困难)

k = 任意整数

由于进行一次交易需要两天, 一天买入一天卖出, 所以 最大交易次数 k <= n/2, 在初始时可以进行判断, 若 k>n/2, 问题转化为 不限次交易, 相当于问题: 122.买卖股票的最佳时机 II(简单)

初始化:

主要需要初始化 第 0 天的情况

dp[i][0][0]:对应于初始状态,第i天0次交易卖出,既然都没交易,那何来卖出呢,所以只能是0。这里不用进行初始化, 因为默认为 0 
dp[0][k][0]: 第1天 未持有股票, 所得最大收益 为 0
dp[0][k][1]: 第1天, 持有股票, 最大收益为 -price[0]

代码:

    class Solution {
        public int maxProfit(int k, int[] prices) {
            int n = prices.length;
            if (n == 0) {
                return 0;
            }
            if (k > n / 2) {
                // 转化为不限次交易
                int dp_i_0 = 0, dp_i_1 = -prices[0];
                for (int i = 1; i < n; i++) {
                    int temp = dp_i_0;
                    dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);
                    dp_i_1 = Math.max(dp_i_1, temp - prices[i]);
                }
                return dp_i_0;
            }
            int[][][] dp = new int[n][k + 1][2];
            // 初始化第 0 天的情况
            for (int i = 0; i <= k; i++) {
                dp[0][i][0] = 0;
                dp[0][i][1] = -prices[0];
            }
            for (int i = 1; i < n; i++) {
                for (int j = 1; j <= k; j++) {
                    dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i]);
                    dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i]);
                }
            }
            return dp[n - 1][k][0];
        }
    }

代码参考: 四种解法+图解 188.买卖股票的最佳时机 IV

2. 121. 买卖股票的最佳时机 (k=1)

转移方程

套上面分析状态转移方程,根据 base case,可以做一些化简

dp[i][1][0] = max(dp[i-1][1][0], dp[i-1][1][1] + prices[i])
dp[i][1][1] = max(dp[i-1][1][1], dp[i-1][0][0] - prices[i]) 
            = max(dp[i-1][1][1], -prices[i])
解释:k = 0 的 base case,所以 dp[i-1][0][0] = 0。

现在发现 k 都是 1,不会改变,即 k 对状态转移已经没有影响了。
可以进行进一步化简去掉所有 k:
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
dp[i][1] = max(dp[i-1][1], -prices[i])

初始化

初始化第 0 天的状态
dp[0][0] = 0
dp[0][1] = -prices[0]

代码:

    class Solution {
        public int maxProfit(int[] prices) {
            if (prices.length == 0) {
                return 0;
            }
            int dp_i_0 = 0, dp_i_1 = -prices[0];
            for (int i = 1; i < prices.length; i++) {
                dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);
                dp_i_1 = Math.max(dp_i_1, -prices[i]);
            }
            return dp_i_0;
        }
    }
3. 122. 买卖股票的最佳时机 II (k 不限次数)

转移方程

套上面分析状态转移方程,根据 base case,可以直接删去 k

dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
            = max(dp[i-1][k][1], dp[i-1][k][0] - prices[i])

我们发现数组中的 k 已经不会改变了,也就是说不需要记录 k 这个状态了:
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])

初始化

初始化第 0 天的状态
dp[0][0] = 0
dp[0][1] = -prices[0]

代码:

    class Solution {
        public int maxProfit(int[] prices) {
            if (prices.length == 0) {
                return 0;
            }
            int dp_i_0 = 0, dp_i_1 = -prices[0];
            for (int i = 1; i < prices.length; i++) {
                int temp = dp_i_0;
                dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);
                dp_i_1 = Math.max(dp_i_1, temp - prices[i]);
            }
            return dp_i_0;
        }
    }
4. 123. 买卖股票的最佳时机 III (k=2)

这里与 188.买卖股票的最佳时机 IV(困难) 题目基本没有区别, 只是 k 指定了为 2, 所以直接令 k=2, 复用 188题的代码即可

转移方程

dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])

初始化

初始化第 0 天的状态
dp[0][k][0] = 0
dp[0][k][1] = -prices[0]

代码:

    class Solution {
        public int maxProfit(int[] prices) {
            int n = prices.length;
            if (n == 0) {
                return 0;
            }
            int k = 2;
            int[][][] dp = new int[n][k + 1][2];
            // 初始化第 0 天的情况
            for (int i = 0; i <= k; i++) {
                dp[0][i][0] = 0;
                dp[0][i][1] = -prices[0];
            }
            for (int i = 1; i < n; i++) {
                for (int j = 1; j <= k; j++) {
                    dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i]);
                    dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i]);
                }
            }
            return dp[n - 1][k][0];
        }
    }

以上代码复杂度较高, 由于这里 k 取值范围比较小,所以可以不用 for 循环,直接把 k = 1 和 2 的情况全部列举出来也可以:

状态转移方程:

dp[i][2][0] = max(dp[i-1][2][0], dp[i-1][2][1] + prices[i])
dp[i][2][1] = max(dp[i-1][2][1], dp[i-1][1][0] - prices[i])
dp[i][1][0] = max(dp[i-1][1][0], dp[i-1][1][1] + prices[i])
dp[i][1][1] = max(dp[i-1][1][1], -prices[i])

代码:

    class Solution {
        public int maxProfit(int[] prices) {
            int n = prices.length;
            if (n == 0) {
                return 0;
            }
            int dp_i10 = 0;
            int dp_i11 = -prices[0];
            int dp_i20 = 0;
            int dp_i21 = -prices[0];
            for (int i = 1; i < n; i++) {
                // 今天未持有: 1.昨天就未持有,今天无操作 2.昨天持有今天卖出
                dp_i20 = Math.max(dp_i20, dp_i21 + prices[i]);
                // 今天持有: 1.昨天就持有, 今天无操作 2.昨天未持有, 今天买入,交易次数+1
                dp_i21 = Math.max(dp_i21, dp_i10 - prices[i]);
                // 下面推交易次数为 1 的情况
                dp_i10 = Math.max(dp_i10, dp_i11 + prices[i]);
                // 交易记录为1 ,那么如果昨天未持有, 今天买入, 那么此次为第一次交易, 原收益为 0 , 直接等于 -prices[i]
                dp_i11 = Math.max(dp_i11, -prices[i]);
            }
            return dp_i20;
        }
    }

5. 309. 最佳买卖股票时机含冷冻期 (k 不限次数, 只是含有冷冻期)

转移方程

每次 sell 之后要等一天才能继续交易。只要把这个特点融入上一题的状态转移方程即可:

dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
dp[i][1] = max(dp[i-1][1], dp[i-2][0] - prices[i])
解释:第 i 天选择 buy 的时候,要从 i-2 的状态转移,而不是 i-1 。

初始化

初始化第 0 天的状态
dp[0][0] = 0
dp[0][1] = -prices[0]

代码:

这个注意需要额外定义一个用于表示 dp[i-2][0] 的变量, 初始时值也为 0 , 具体在循环体内的赋值, 可以先弄清楚各个变量的含义, 然后在草稿纸上演算一下, 是否代表其正确的值, 避免出错

    class Solution {
        public int maxProfit(int[] prices) {
            if (prices.length == 0) {
                return 0;
            }
            // 初始化第 0 天的情况
            int dp_i_0 = 0;
            int dp_i_1 = -prices[0];
            // 代表 dp[i-2][0], 初始时为 0
            int dp_pre_0 = 0;
            for (int i = 1; i < prices.length; i++) {
                int temp = dp_i_0;
                dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);
                dp_i_1 = Math.max(dp_i_1, dp_pre_0 - prices[i]);
                dp_pre_0 = temp;
            }
            return dp_i_0;
        }
    }
6. 714. 买卖股票的最佳时机含手续费 (k 不限次数, 每次买入需要减手续费, 与 122 题类似)

转移方程

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

初始化

初始化第 0 天的状态
dp[0][0] = 0
dp[0][1] = -prices[0] - fee

代码:

    class Solution {
        public int maxProfit(int[] prices, int fee) {
            if (prices.length == 0) {
                return 0;
            }
            int dp_i0 = 0;
            int dp_i1 = -prices[0] - fee;
            for (int i = 1; i < prices.length; i++) {
                int temp = dp_i0;
                dp_i0 = Math.max(dp_i0, dp_i1 + prices[i]);
                dp_i1 = Math.max(dp_i1, temp - prices[i] - fee);
            }
            return dp_i0;
        }
    }

总体思路参考: 团灭 LeetCode 股票买卖问题—labuladong的算法小抄

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值