代码随想录算法训练营day55 || 309.最佳买卖股票时机含冷冻期,714.买卖股票的最佳时机含手续费

文章讲述了如何利用动态规划解决股票买卖问题,引入冷冻期的概念,定义了三种状态(不持有、持有和冷冻期),详细解释了状态转移规则和代码实现,涉及时间复杂度和空间复杂度分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

动态规划来决定最佳时机,这次有冷冻期!| LeetCode:309.买卖股票的最佳时机含冷冻期_哔哩哔哩_bilibili

动态规划来决定最佳时机,这次含手续费!| LeetCode:714.买卖股票的最佳时机含手续费_哔哩哔哩_bilibili

 309.最佳买卖股票时机含冷冻期

思路:本题增加了一种冷冻期的量,引出一种全新的状态。我所理解的是本题增加了一个状态,是冷冻状态。代码随想录 对将本题细分为了四个状态。我个人理解上还是认为三个状态更加好理解,但是的确三个状态之中存在一个疑惑,这点我们一会儿来说明。

首先股票类题目可以总结出这样的几个前提:

  • dp[i][k] 表示的是当天之后处于什么样的状态且拥有怎样的最大收益,且每天结束时与后一天开始时是等价的;
  • 每一天的收益与状态是与依赖前一天所转化的,这不仅是与现实逻辑——要一天天过相通;同时也是保证每一次状态转移是贴合题目逻辑也是可以形成形式化的操作。所以第i天不会涉及到i-2天的内容,而i-2天的结果一定是传递到了i-1天的结果中,然后再传递到i天的结果中;

现在给出三个状态:0表示不持有股票;1表示持有股票;2表示处于冷冻期;

相应的dp[i][0],第i天结束后处于不持有股票状态且不处于冷冻期;dp[i][1]表示结束时处于持有股票状态;dp[i][2]表示今天结束之后处于冷冻期状态且不持有股票。三个状态的值之间转换是这样,注意我们所考虑的i一定是参考i-1及其之前的,比参考i的,所以第i天卖了股票后状态就变成2了,且后一天一定也是2,这一点三种状态的核心争议点的来源,明确这一点后三种状态就比较清晰了。

  • dp[i][1] = max(dp[i-1][1], dp[i-1][0]-prices[i],第i天结束后处于持有股票的状态;要么延续买了前一天有股票状态;要么可以买入然后买了股票,这其中包括前一天是0或者前两天是2;
  • dp[i][2] = dp[i-1][1]+prices[i],第i天结束之后处于冷冻状态,那么一定只有卖了股票才会出现冷冻期状态。此外这里0和2一定是独立分开的,所以如果卖了股票是处于冷冻期,那么一定不会转换到不是冷冻期的0,所以0状态与1状态之间没有关联的可能。并且冷冻期只会是一天,i-1是冷冻期,则第i天也是冷冻期。
  • dp[i][0] = max(dp[i-1][0], dp[i-1][2]) 第i天结束后处于不持有且不冷冻的状态,那么有两种来源,其一是买了股票持有,与不持有相悖,所以0不会从1状态转换过来;所以只会是延续前一天,或者前一天是卖出了股票或者是冷冻期,那么今天结束时就会恢复到可以购买,即后一天可以购买,那么也可以反过来等效为今天结束时冷冻期已经解冻了
// 时间复杂度O(n)
// 空间复杂度O(3*n)

class Solution {
    public int maxProfit(int[] prices) {
        // 具有三种状态
        // dp[i][0] 表示处于不持有股票的状态,且不处于冷冻期
        // dp[i][1] 表示持有股票
        // dp[i][2] 表示不持有股票且处于冷冻期
        int n = prices.length;
        int[][] dp = new int[n][3];

        dp[0][0] = 0;
        dp[0][1] = -prices[0];
        dp[0][2] = 0;

        for(int i=1; i<n; i++){
            // dp[i][0/1/2]表示的都是第i天结束之后的最大收益

            // 持有股票,可以是第i-1天结束后就持有股票;也可以是i-1天结束后处于不持有股票且不处于冷冻期在第i天进行了股票买入
            if(i > 2)
                dp[i][1] = Math.max(dp[i-1][1], Math.max(dp[i-1][0]-prices[i], dp[i-2][2]-prices[i]));
            else
                dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0]-prices[i]);
            // 不持有股票且不是冷冻期,可以是第i-1天结束后是不持有股票且不处于冷冻期,第i天继续不持有;也可以是第i-1天结束后处于了冷冻期,则今天一天处于冷冻期,但是今天结束后就不是冷冻期
            dp[i][0] = Math.max(dp[i-1][0], dp[i-1][2]);
            // 处于冷冻期,一定是第i天卖出了股票,若第i-1天处于冷冻期,则第i天冷冻期已经结束了
            dp[i][2] = dp[i-1][1]+prices[i];
        }

        return Math.max(dp[n-1][0], dp[n-1][2]);
    }
}

如果使用三个状态一定要明确dp[i][k]表示的是当天结束时处于了什么状态,当天的操作已经全部完成了;第二当天结束时的状态是等效与后一天开始时的状态的,即后一天可以做什么操作,在当天结束时就可以做同样的所有操作(比如结束时是2,当前与后一天都是冷冻,但后一天的结束时就是0了,与后两天开始是0保持一致,后两天开始时可以做的所有操作在后一天结束时就已经可以做了;结束时是1,则后一天可以卖,也可以什么都不操作;结束时是0,则后一天可以买入,也可以什么都不操作)

另解

// 时间复杂度O(n)
// 空间复杂度O(2n+2)

class Solution {
    public int maxProfit(int[] prices) {
        // 买入股票的最佳时机II 的每一步卖出时加入处理手续费的操作即可
        int n = prices.length;

        // 扩展为n+1是为了更好的处理i-2的操作
        int[][] dp = new int[n+1][2];
        // 初始化,第0天拥有股票显然是不合法的
        dp[0][1] = Integer.MIN_VALUE;
        dp[0][0] = 0;

        // 注意这里的i是表示天数,而不是数组的索引下标,且初始化时没有使用任一数组内的元素,所以访问数组需要i-1
        for(int i=1; i<=n; i++){
            if(i>1)
                dp[i][1] = Math.max(dp[i-1][1], dp[i-2][0]-prices[i-1]);// 持有状态只会是延续前一天或者是来源于不算冷冻期的最近一次不持有状态,直接减去2,这其中依赖于递推一定是一步一步从头或从尾进行遍历的
            else
                dp[i][1] = Math.max(dp[i-1][1], -prices[i-1]);          // 第一天结束处于持有状态,并且更新为合法的值
            dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1]+prices[i-1]);
        }
        return dp[n][0];
    }
}

714.买卖股票的最佳时机含手续费

思路:就是在 122.买卖股票的最佳时机II 中每次卖出股票时增加手续费的操作即可求解。

// 时间复杂度O(n)
// 空间复杂度O(2*n)

class Solution {
    public int maxProfit(int[] prices, int fee) {
        // 买入股票的最佳时机II 的每一步卖出时加入处理手续费的操作即可
        int n = prices.length;

        int[][] dp = new int[n][2];
        dp[0][1] = -prices[0];
        dp[0][0] = 0;

        for(int i=1; i<n; i++){
            dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0]-prices[i]);
            dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1]+prices[i]-fee);
        }
        return dp[n-1][0];
    }
}

### 代码随想录算法训练营 Day20 学习内容与作业 #### 动态规划专题深入探讨 动态规划是一种通过把原问题分解为相对简单的子问题的方式来求解复杂问题的方法[^1]。 #### 主要学习内容 - **背包问题系列** - 背包问题是典型的动态规划应用场景之一。这类题目通常涉及给定容量的背包以及一系列具有不同价值和重量的物品,目标是在不超过总容量的情况下最大化所选物品的价值。 - **状态转移方程构建技巧** - 构建合适的状态转移方程对于解决动态规划问题是至关重要的。这涉及到定义好dp数组(或表格),并找到从前一个状态到下一个状态之间的关系表达式[^2]。 - **优化空间复杂度方法** - 对于某些特定类型的DP问题,可以采用滚动数组等方式来减少所需的空间开销,从而提高程序效率[^3]。 #### 实战练习题解析 ##### 题目:零钱兑换 (Coin Change) 描述:给定不同面额的硬币 coins 和一个总金额 amount。编写函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 `-1`。 解决方案: ```python def coinChange(coins, amount): dp = [float('inf')] * (amount + 1) dp[0] = 0 for i in range(1, amount + 1): for coin in coins: if i >= coin and dp[i - coin] != float('inf'): dp[i] = min(dp[i], dp[i - coin] + 1) return dp[-1] if dp[-1] != float('inf') else -1 ``` 此段代码实现了基于自底向上的迭代方式解决问题,其中 `dp[i]` 表示达到金额 `i` 所需最小数量的硬币数目[^4]。 ##### 题目:完全平方数 (Perfect Squares) 描述:给出正整数 n ,找出若干个不同的 完全平方数 (比如 1, 4, 9 ...)使得它们的和等于n 。问至少需要几个这样的完全平方数? 解答思路同上一题类似,只是这里的“硬币”变成了各个可能的完全平方数值。 ```python import math def numSquares(n): square_nums = set([i*i for i in range(int(math.sqrt(n))+1)]) dp = [float('inf')] *(n+1) dp[0] = 0 for i in range(1,n+1): for sq in square_nums: if i>=sq: dp[i]=min(dp[i],dp[i-sq]+1); return dp[n]; ``` 这段代码同样运用了动态规划的思想去寻找最优解路径,并利用集合存储所有小于等于输入值的最大平方根内的平方数作为候选集[^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值