LeetCode 121、122、123、188、309、714. 买卖股票的最佳时机(及Ⅱ、Ⅲ、Ⅳ、含冷冻期、含手续费)

买卖股票的最佳时机系列问题是一个动态规划问题,可以抽象出一个动态规划的模板来解决此类问题。因为每一天只有三种选择:买入、卖出、什么都不做。限制的状态有:买卖的次数、手续费、冷冻期等。但是对于限制条件只有买卖至多一次和不限制次数这两种情况,可以有更为简单的方法,如下面买卖股票问题的前两题:121、122题。但是如果限制买卖次数为2次、3次、5次…k次这些情况就需要用动态规划来解决了。总结模板的思路看此博客:股票问题系列通解(转载翻译),下面用的解答方法没有用到此模板。

一、LeetCode 121.买卖股票的最佳时机

描述

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
在这里插入图片描述

思路

题目中规定了是求最多买卖1次的最大利润。比较简单的办法是双指针,设置两个left和right指针,left记录买入的最低价格,那么可以使用right指针遍历每一天,判断right当天的价格是否比left那天的价格更低,如果更低则说明left那天买入不如今天买入,则让left更新为right的值,如果right当天价格大于left那天,说明现在right当天卖出会有利润,则判断利润是否大于之前的利润值,再决定是否更新maxProfit的值。这样遍历到最后一天,便可以得到最大利润值。最终结果:(就1次遍历,咋这么耗时耗内存…)
在这里插入图片描述

解答

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int maxProfit = 0;//记录最大利润
        //双指针,left和right指针,left记录买入的最低价格
        for(int l = 0, r = 1;r < prices.size(); ++ r){
            if(prices[r] < prices[l]) l = r;
            else
                maxProfit = max(maxProfit, prices[r] - prices[l]);
        }
        return maxProfit;
    }
};

二、LeetCode 122.买卖股票的最佳时机Ⅱ

描述

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
在这里插入图片描述

思路

这道题变成了不限制买卖的次数时求最大利润,唯一的限制是买入之后必须先卖出才能继续买入。最简单的思路就是贪心算法,只需要考虑今天买入明天卖出能否获利即可,只要能获利则今天买入明天卖出,不考虑后天的事。这样一定会在获利最大的情况下进行最多的买卖次数,因为可能会在当天卖出之后再次重新买入。

解答

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int len = prices.size();
        int sum = 0;//总利润
        for(int i=0;i < len-1;++i)//贪心算法
            if(prices[i+1]-prices[i] > 0) sum += prices[i+1] - prices[i];
        return sum;
    }
};

三、LeetCode 123.买卖股票的最佳时机Ⅲ

描述

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
在这里插入图片描述

思路

解答来自评论,是不太标准的动态规划解法。使用firstBuy、firstSell记录第一次买入、卖出后手中剩余的钱;secondBuy和secondSell记录第二次买入、卖出后手中剩余的钱。

解答

class Solution {
public:
    int MIN_VALUE = -2147483648;
    int maxProfit(vector<int>& prices) {
        int firstBuy = MIN_VALUE, firstSell = 0;//第1次买入、卖出后手中剩余的钱
        int secondBuy = MIN_VALUE, secondSell = 0;//第2次买入、卖出后手中剩余的钱
        for(int i = 0;i != prices.size(); ++ i){
            firstBuy = max(firstBuy, -prices[i]);
            firstSell = max(firstSell, firstBuy + prices[i]);
            secondBuy = max(secondBuy, firstSell - prices[i]);
            secondSell = max(secondSell, secondBuy + prices[i]);
        }
        return secondSell;
    }
};

四、LeetCode 188.买卖股票的最佳时机Ⅳ

描述

给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
在这里插入图片描述

思路

在上一题的基础上稍加改动,单独将第一次买入卖出拿出来计算。

解答

class Solution {
public:
    int MIN_VALUE = -2147483648;//int最小值
    int maxProfit(int k, vector<int>& prices) {
        if(k == 0) return 0;//允许交易次数为0次直接返回0
        vector<int> buy(k+1, MIN_VALUE);//购买数组,下标记录第几次买入
        vector<int> sell(k+1, 0);//卖出数组,下标记录第几次卖出

        for(int i = 0;i != prices.size(); ++ i){
            buy[1] = max(buy[1], -prices[i]);//第1次买入卖出比较特殊,单独算,k从1开始
            sell[1] = max(sell[1], buy[1] + prices[i]);
            for(int j = 2;j != k + 1;++ j){      
                buy[j] = max(buy[j], sell[j-1] - prices[i]);
                sell[j] = max(sell[j], buy[j] + prices[i]);
            }
        }
        return sell[k];
    }
};

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

描述

给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。​
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

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

在这里插入图片描述

思路

动态规划,每天只能有一种操作,三个数组记录三种状态。

解答

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        if(n == 0) return 0;
        vector<int> buy(n, 0);//记录到第i天为止,之前所有天的最后操作为买入时的最大收益
        vector<int> sell(n, 0);//记录第i天操作为卖出时的最大收益
        vector<int> freeze(n, 0);//记录到第i天为止最后操作是冻结时(无操作)的最大收益
        
        buy[0] = -prices[0];
        //每天只能有一种操作:买入、卖出、无操作
        for(int i = 1;i < n;++ i){
            buy[i] = max(freeze[i - 1] - prices[i], buy[i - 1]);//第i天买入或之前就已经买入
            sell[i] = buy[i - 1] + prices[i];//第i天卖出
            freeze[i] = max(sell[i - 1], freeze[i - 1]);//第i-1天卖出或者第i-1天无操作
        }

        return max(sell[n - 1], freeze[n - 1]);
    }
};

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

描述

给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;非负整数 fee 代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
返回获得利润的最大值。
注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
在这里插入图片描述

思路

这里注意一点,针对买入时就扣除手续费的情况,下面两句:notHave = max(notHave, have + prices[i] - fee);
have = max(have, notHave - prices[i]);,有人会有疑问,notHave更新为have + prices[i] - fee的值后,那么have用的不就是notHave更新后的值了吗,不应该用notHave之前的值吗?假设have+price>notHave,移项得notHave-price<have,那么notHave-price-fee更是<have,所以第二步是一定不会更新have的值的;如果have+price<notHave,那么notHave不更新,第二步have还是用的之前的notHave值来更新。对于卖出时扣手续费的情况类似。

解答

买入时就扣除手续费。

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        int n = prices.size();
        if (n < 2) return 0;
        int notHave = 0;//手上无股票的最大收入
        int have = -prices[0] - fee;//手上有股票的最大收入
        
        for(int i = 1;i < n; ++ i){
            //第i([0, n-1])天交易结束时,手里无股票:1.要么当天不交易;2.要么之前买了股票今天卖出,减去手续费
            notHave = max(notHave, have + prices[i]);
            //第i天结束时,手里有股票:1.要么之前已经买了股票,今天不卖;2.或者之前没有买股票,今天买入
            have = max(have, notHave - prices[i] - fee);
        }
        return notHave;//最终手里没股票的最大利润
    }
};

或者卖出时扣除手续费。

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        int n = prices.size();
        if (n < 2) return 0;
        int notHave = 0;//手上无股票的最大收入
        int have = -prices[0];//手上有股票的最大收入
        
        for(int i = 1;i < n; ++ i){
            //第i([0, n-1])天交易结束时,手里无股票:1.要么当天不交易;2.要么之前买了股票今天卖出,减去手续费
            notHave = max(notHave, have + prices[i] - fee);
            //第i天结束时,手里有股票:1.要么之前已经买了股票,今天不卖;2.或者之前没有买股票,今天买入
            have = max(have, notHave - prices[i]);
        }
        return notHave;//最终手里没股票的最大利润
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值