• 力扣309——最佳买卖股票时机含冷冻期


    这道题主要涉及状态转移方程,想清楚所有状态后,就可以轻松解决。

    原题

    给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。

    设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

    • 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
    • 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

    示例:

    输入: [1,2,3,0,2]
    输出: 3 
    解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
    

    原题url:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/

    解题

    暴力解法

    一开始我就是想着一共有几种状态,这几种状态分别可以转换为哪些状态:

    1. 当前是"持股状态",可以选择继续不交易,保持"持股状态",或者选择卖掉之后变成"冷冻状态";
    2. 当前是"冷冻状态",只能选择继续不交易,变成"不持股状态";
    3. 当前是"不持股状态",可以选择继续不交易,保持"不持股状态",或者选择买股票之后变成"持股状态";

    我增加了最后终止条件:如果是最后一天,并且手上持有股票的话,必须卖出,这样可以保证最终利益最大。

    接下来看看代码:

    class Solution {
        int max = 0;
    
        public int maxProfit(int[] prices) {
            if (prices.length == 0) {
                return 0;
            }
    
            recursiveBuy(-1, false, 0, 0, prices);
            return max;
        }
    
        public void recursiveBuy(
            int prePrice,
            boolean cooldown,
            int profit,
            int index,
            int[] prices) {
            // 如果到了最后一天,并且手上持有股票的话,必须卖掉
            if (index == prices.length - 1) {
                if (prePrice >= 0 && !cooldown) {
                    profit = profit + prices[index];
                }
                max = Math.max(max, profit);
                return;
            }
    
            // 当前持有股票
            if (prePrice >= 0) {
                // 此时可以选择不交易,或者卖掉
                // 不交易
                recursiveBuy(prePrice, cooldown, profit, index + 1, prices);
                // 卖掉
                recursiveBuy(-1, true, profit + prices[index], index + 1, prices);
    
                return;
            }
    
            // 当前不持有股票,可以被动不交易、主动不交易、买
    
            // 如果处于冷冻期,只能被动不交易
            if (cooldown) {
                recursiveBuy(prePrice, false, profit, index + 1, prices);
                return;
            }
    
            // 不交易
            recursiveBuy(prePrice, cooldown, profit, index + 1, prices);
            // 买
            recursiveBuy(prices[index], cooldown, profit - prices[index], index + 1, prices);
        }
    }
    

    报了超出时间限制,好的,我们想想怎么优化。

    状态转移方程

    上面暴力解法之所以会超时,因为重复计算了。我一开始的想法是想着记录中间结果,但越想越复杂,忍不住看了别人的思路,真的是让我豁然开朗。那就是状态转移方程

    之前我上面提到的是所有状态可以变成哪些状态,但其实有些地方想的是不清楚的。我们用箭头连接两个状态,箭头开始的那端表示前一天的状态,箭头终止的那端表示当天的状态,那么其内容为:

    因为买和卖只是两个操作,我们认为只能在每一天的0点执行,当天的状态就由0点之后的状态来表示。

    • "冷冻期"状态只能是昨天刚买了股票,也就是"不持股"状态转移过来。
    • "不持股"状态可以由自己,或者昨天是"持股"状态,今天卖掉,转移过来。
    • "持股"状态可以由自己,或者昨天是"冷冻期"状态,今天买了,转移过来。

    你可能会问,如果这样表示状态转移方程的话,那么第一天可以买入股票就没法解释了。那简单,为了配合这种特殊情况,我们再记录一个更早一天的不持股状态,这样就可以满足了。

    接下来看看代码:

    class Solution {
        public int maxProfit(int[] prices) {
            if (prices.length < 2) {
                return 0;
            }
    
            // 因为每次只涉及到前一天的三个状态值,因此只要三个数字记录即可
            /**
             * 其状态转移方程为:
             * "冷冻期"只能由"不持股"转换而来。
             * "持股"可以由"持股"和"冷冻期"转换而来。
             * "不持股"可以由"不持股"和"持股"转换而来。
             */
             // 定义初始情况
             // 不持股
             int noStock = 0;
             // 持股
             int hasStock = -prices[0];
             // 冷冻期
             int cooldown = 0;
             // 上一次的不持股
             int beforeNoStock = 0;
    
             for (int i = 1; i < prices.length; i++) {
    				     // "不持股"可以由"不持股"和"持股"转换而来。
                 noStock = Math.max(beforeNoStock, hasStock + prices[i]);
    						 // "持股"可以由"持股"和"冷冻期"转换而来。
                 hasStock = Math.max(hasStock, cooldown - prices[i]);
    						 // "冷冻期"只能由"不持股"转换而来。
                 cooldown = beforeNoStock;
    						 // 更新一下"上一次的不持股"状态
                 beforeNoStock = noStock;
             }
    
             return Math.max(noStock, cooldown);
        }
    }
    

    提交OK。

    状态转移方程继续优化

    其实从上面的分析,你隐约可以察觉到,"冷冻期"就是一种特殊的"不持股"状态。根据上面的结论,当你想买股票时,要求的是必须连续两天"不持股",这点你想通了吗?可能也正因为这一点,我们在上面的代码中才需要记录"上一次的不持股"状态。

    既然这样,我们干脆就简化为持股状态和不持股状态两种,其状态转移方程可以描述为:

    • 持股状态可以由自己,或者连续两天为不持股状态,今天买了股票,转移而来。
    • 不持股状态可以由自己,或者前一天为持股状态,今天卖了股票,转移而来。

    因为我们记录的是每一天状态所对应的收入,那么所谓的连续两天为不持股状态,就是相当于从两天前收入不变。

    接下来看看代码:

    class Solution {
        public int maxProfit(int[] prices) {
            if (prices.length < 2) {
                return 0;
            }
    
            // 这次只有两个状态,但需要记录两天前的不持股收入
            // 定义初始情况
            // 不持股
            int noStock = 0;
            // 持股
            int hasStock = -prices[0];
            // 上一次的不持股
            int beforeNoStock = 0, temp;
    
            for (int i = 1; i < prices.length; i++) {
    				    // 记录一下"两天前的不持股"收入
    				    temp = noStock;
                // "不持股"可以由"不持股"和"持股"转换而来。
                noStock = Math.max(noStock, hasStock + prices[i]);
                // "持股"可以由"持股"和"冷冻期"转换而来。
                hasStock = Math.max(hasStock, beforeNoStock - prices[i]);
                // 更新一下"上一次的不持股"状态
                beforeNoStock = temp;
            }
    				
    				// 最大值一定是最后一天不持股的情况
            return noStock;
        }
    }
    

    提交OK。

    总结

    以上就是这道题目我的解答过程了,不知道大家是否理解了。状态转移应该还是很经典的方法,主要在于是否可以想出所有状态及其转化关系。

    有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。

    https://death00.github.io/

    公众号:健程之道

  • 相关阅读:
    024.Zabbix告警等级机制
    023.Zabbix自定义(邮箱)脚本告警-02
    022.Zabbix自定义(邮箱)脚本告警01
    021.Zabbix的邮件告警-01
    020.Zabbix的Actions配置
    019.Zabbix的Trigger及相关函数
    018.Zabbix维护时间和模板导入
    017.Zabbix宏介绍
    016.Zabbix聚合监控
    015.Zabbix的日志监控配置
  • 原文地址:https://www.cnblogs.com/death00/p/12194019.html
Copyright © 2020-2023  润新知