股票问题总结

123 阅读4分钟

一、121. 买卖股票的最佳时机 - 力扣(LeetCode)

题目要求: 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

解法一:贪心

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int low = INT_MAX;
        int result = 0;
        for (int i = 0; i < prices.size(); i++) {
            low = min(low, prices[i]);  // 取最左最小价格
            result = max(result, prices[i] - low); // 直接取最大区间利润
        }
        return result;
    }
};

解法二:动态规划(我自己写的)

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int len = prices.size();
        if(len < 2)
            return 0;
        vector<int>dp(len);//dp[i]的含义是第i天卖出股票所得到的最大收益
        dp[0] = 0;
        int ans = 0;
        for(int i = 1;i < len;i ++)
        {
            int val = prices[i] - prices[i - 1];
            if(val > 0)
            {
                dp[i] = dp[i - 1] + val;
            }else{
                dp[i] = dp[i - 1] + val > 0?dp[i - 1] + val:0;
            }
            ans = max(ans,dp[i]);
        }
        return ans;
    }
};

动态规划(看的题解)

dp[i] [0]表示第i天不持有股票所得最多现金

dp[i] [1]表示第i天持有股票所得最多现金

注意这里说的是“持有”,“持有”不代表就是当天“买入”!也可能是之前就买入了,今天保持持有的状态

确定递推公式

如果第i天持有股票即dp[i] [1], 那么可以由两个状态推出来

  • 第i-1天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1] [1]
  • 第i天买入股票,所得现金就是买入今天的股票后所得现金即:-prices[i]

那么就应该选所得现金最大的,所以dp[i] [1] = max(dp[i - 1] [1], -prices[i]);

如果第i天不持有股票即dp[i] [0], 也可以由两个状态推出来

  • 第i-1天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1] [0]
  • 第i天卖出股票,所得现金就是按照今天股票价格卖出后所得现金即:prices[i] + dp[i - 1] [1]

同样也取最大的,dp[i] [0] = max(dp[i - 1] [0], prices[i] + dp[i - 1] [1]);

// 版本一
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int len = prices.size();
        if (len == 0) return 0;
        vector<vector<int>> dp(len, vector<int>(2));
        dp[0][1] -= prices[0];
        dp[0][0] = 0;
        for (int i = 1; i < len; i++) {
            dp[i][1] = max(dp[i - 1][1], -prices[i]);
            dp[i][0] = max(dp[i - 1][0], prices[i] + dp[i - 1][1]);
        }
        return dp[len - 1][0];
    }
};

二、122. 买卖股票的最佳时机 II - 力扣(LeetCode)

题目要求: 给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。

返回 你能获得的 最大 利润 。

和上一题相比,就是可以买卖多次了。

唯一区别就在递推公式上:

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]);//两个来源:之前就一直持有,或者是原本没有持有,刚刚买入的

当然这个题用贪心更好理解。

三、123. 买卖股票的最佳时机 III - 力扣(LeetCode)

题目要求:[定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

这个题比较难,因为有四种状态,思路见注释:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int size = prices.size();
        if(size == 1)
        return 0;
        //分为5种状态:1、没有操作发生。2、第一次买入。3、第一次卖出。4、第二次买入。5、第二次卖出
        //没有操作发生的时候,数组的值一定是0,所以就不单独列出来了
        vector<vector<int>>dp(size,vector<int>(4));
        //下面进行初始化
        dp[0][0] = -prices[0];//dp[i][0]代表的是当前持有股票,且买入之前没有发生交易。也就是第一次买入
        dp[0][1] = 0;//第一次卖出。卖出肯定是要盈利的,如果不能盈利那就是0。也就是代表了当前没有持有股票,且之前发生了一次交易的最大利润
        dp[0][2] = -prices[0];//这里有必要单独解释一下。此时代表了当前持有股票,且买入之前发生了一次交易。也就是第二次买入,第二次买入是在第一次卖出的基础上进行的,第一次卖出初始化为0,所以第二次买入就是-price[0]
        dp[0][3] = 0;//代表了当前没有持有股票,之前已经发生过两次交易的最大利润,也就是第二次卖出
        for(int i = 1;i < size;i ++)
        {
            dp[i][0] = max(dp[i - 1][0],-prices[i]);
            dp[i][1] = max(dp[i - 1][1],dp[i - 1][0] + prices[i]);
            dp[i][2] = max(dp[i - 1][2],dp[i - 1][1] - prices[i]);
            dp[i][3] = max(dp[i - 1][3],dp[i - 1][2] + prices[i]);
        }
        return dp[size - 1][3];//最后一定是第二次卖出的时候利润最大,两次卖出的状态现金最大一定是最后一次卖出。
        //因为如果一次卖出就可以获得最大利润的话,那么第二次我们可以选择不买不卖。第二次卖出的情况其实已经包含了第一次卖出的情况
    }
};

四、188. 买卖股票的最佳时机 IV - 力扣(LeetCode)

题目要求: 给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。

解析:

和上一题相比,这一题只是把交易的次数给做成了一个变量,上一题的交易次数是一个常量2。

解题思路是完全一样的。

一共分为2*k + 1种状态,分别是没有操作(利润就是0),第一次买入,第一次卖出,第二次......第k次买入,第k次卖出。

使用二维数组 dp[i] [j] :第i天的状态为j,所剩下的最大现金是dp[i] [j]

j的状态表示为:

  • 0 表示不操作
  • 1 第一次买入
  • 2 第一次卖出
  • 3 第二次买入
  • 4 第二次卖出

所以,奇数天是买入,偶数天是卖出。

其他思路和上一题一模一样,具体见注释:

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        //和上一题相比,可以交易的次数从2变成了k,成为了一个变量而非常量,解题思路并没有什么区别
        int len = prices.size();
        if(len < 2 || k < 1)
            return 0;
        vector<vector<int>>dp(len,vector<int>(2*k + 1));//dp[i][j]如果j是奇数,就代表的是第(j/2) + 1次买入,比如dp[0][1]代表第一次买入,dp[0][3]代表第二次买入
        //如果j是偶数,就代表j/2次卖出,比如dp[i][2]代表第1次卖出
        for(int i = 0;i <= 2*k;i ++)//初始化
        {
            if(i%2 == 1)//奇数,代表买入
            {
                dp[0][i] = - prices[0];
            }else{
                dp[0][i] = 0;
            }
        }
        for(int i = 1;i < len;i ++)//遍历dp数组
        {
            for(int j = 1;j <= 2*k;j ++)
            {
                
                if(j%2 == 1)//奇数,代表买入.可能是早就买入了,也可能是刚刚买入。如果是第i次刚刚买入,那么就应该是第i- 1次卖出的价格减去prices[i]
                {
                    dp[i][j] = max(dp[i - 1][j],dp[i - 1][j - 1] - prices[i]);
                }else{
                    dp[i][j] = max(dp[i - 1][j],dp[i - 1][j - 1] + prices[i]);//偶数代表卖出.可能是早就卖出了,也可能是刚刚卖出。如果是第i次刚刚卖出,那么就应该是第i- 1次买入的价格加上prices[i]
                }
            }
        }
        return dp[len - 1][2*k];
    }
};

五、309. 最佳买卖股票时机含冷冻期 - 力扣(LeetCode)

题目要求: 给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 。

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

卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

解析:

这个题目就是要注意,卖了之后不能立刻就再买入,至少要等一天。当前买入的话,至少要两天之前卖出才行。明白这一点就OK了。主要就在递推公式上

代码:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int len = prices.size();
        if(len < 2)
            return 0;
        vector<vector<int>>dp(len,vector<int>(2));//dp[i][0]代表着当前不持有股票(也就是卖出状态)所能获得的最大金额,dp[i][1]代表了当前持有股票(也就是买入状态)所能获得的最大利润
        dp[0][0] = 0;
        dp[0][1] = -prices[0];
        dp[1][0] = max(dp[0][0],prices[1] + dp[0][1]);
        dp[1][1] = max(dp[0][1],- prices[1]);
        for(int i = 2;i < len;i ++)
        {
            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]);//关键点就在这里,由于存在冷冻期,所以买入的前提必须是至少两天前就完成了交易
        }
        return dp[len - 1][0];
    }
};

六、714. 买卖股票的最佳时机含手续费 - 力扣(LeetCode)

题目要求: 给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。

你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。

返回获得利润的最大值。

注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。

easy:

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        int len = prices.size();
        if(len < 2)
            return 0;
        vector<vector<int>>dp(len,vector<int>(2));//还是老规矩,dp[i][0]代表第i天不持有股票的最大利润,dp[i][1]代表持有股票的最大利润
        dp[0][0] = 0;
        dp[0][1] = - prices[0] - fee;
        for(int i = 1;i < len;i ++)
        {
            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] - fee);
        }
        return dp[len - 1][0];
    }
};

这道题还可以用贪心来做,思路见注释:

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        int len = prices.size();
        if(len < 2)
            return 0;
        //贪心做法
        int back = prices[0];//back指的是在后面那个被减的数
        int front = prices[0];//front是前面那个减数,就是更大的数
        bool flag = false;//用来标记是否已经交过fee了,交过就是true,就不用再交了
        int ans = 0;
        for(int i = 1;i < len;i ++)
        {
            if(prices[i] < back && flag == false)//没教过手续费,说明不在买卖之中,可以更新back
            {
                back = prices[i];
            }
            if(prices[i] > back + fee && flag == false)//有利可图
            {
                ans += prices[i] - back - fee;
                back = prices[i];
                flag = true;//置为true,这笔交易里后面就不用再交了
            }
            if(prices[i] > back && flag == true)
            {
                ans += prices[i] - back;
                back = prices[i];
            }
            if(prices[i] < back - fee)//出现了更小的坡谷,
            {
                back = prices[i];
                flag = false;//要重新开始买卖了。新的买卖要交手续费的
            }
        }
        return ans;
    }
};