leetcode六道买卖股票问题

买卖股票系列是动态规划的经典题目,六种变型

一、买卖股票的最佳时机 I

leetcode 121题

题目描述

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。
注意:你不能在买入股票前卖出股票。
【注意】:只有一次交易机会,那么这道题就简化了许多。

我们只需要用一个数组来维护即可,数组 dp[i] 表示在第 i 天卖出能获得的最大利润,用一个变量minPrice来记录 i 以前的最小价格,那么我们只需在价格最小时买入,在第 i 天卖出即可获得 dp[i],最后找出最大的dp[i]

代码

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if(prices.empty()) return 0;
        vector<int> nums(prices.size(), 0);
        int profitMax = 0;
        int minPrice = prices[0];
        for(int i = 1; i < prices.size(); i++) {
            minPrice = min(prices[i], minPrice );
            dp[i] = prices[i] - nums[i];
            if(dp[i] > profitMax) profitMax = dp[i];
        }
        return profitMax;
    }
};

这里可以对空间复杂度进行优化,因为dp数组是不需要的,构建 dp[i] 的时候是用不上dp[0]到dp[i-1]的,所以我们只需要在遍历时更新profitMax即可,根本无需使用dp数组。

代码

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if(prices.empty()) return 0;
        int minPrice = prices[0], profitMax = 0;
        for(int i = 1; i < prices.size(); i++) {
            profitMax = max(profitMax, prices[i] - minPrice );
            minPrice = min(minPrice , prices[i]);
        }
        return profitMax;
    }
};

二、买卖股票的最佳时机 II

leetcode 122题

题目描述

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

这道题相比于前一道变化在于允许多次交易,但每次购买股票前需要出售掉之前的股票,即最多只允许手里存在一只股票。

解法一:贪心

因为对买卖次数没有限制,所以可以直接这么判断:对于第 i 天,如果第 i+1 天的价格 > 第 i 天的价格,那么就在第 i 天买入, 第 i+1 天卖出,如果第 i+2 天的价格 > 第 i+1 天的价格,那么就在第 i+1 天接着买入,在第 i+2 天卖出,其本质与第 i 天买入,第i+2天卖出是一样。

代码

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int profitMax = 0;
        for(int i = 0; i < prices.size()-1; i++) {
            if(prices[i+1] > prices[i]) profitMax += prices[i+1] - prices[i];
        }
        return profitMax;
    }
};

解法二:动态规划

构建一个二维数组 dp[2][len],其中dp[0][i] 表示第 i 天持有现金所获取的最大利润,dp[1][i] 表示第 i 天持有股票所获取的最大利润

边界:

dp[0][0] = 0,dp[1][0] = -prices[0]

状态转移方程:

		dp[0][i] = max{ dp[0][i-1],  d[1][i-1] + prices[i] }
		dp[1][i] = max{ dp[0][i-1] - prices[i],  dp[1][i-1] }

代码

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

三、买卖股票的最佳时机 III

leetcode 123题

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

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

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

限制交易次数为2次,这里我们可以把交易次数2,分解为前后两次,对于 0, 1, 2 … n-1 这 n 天,找寻一个 i ,使得在 0 - i 之间交易一次,在 i - n-1 之间交易一次,求这样的 i ,使得 这两次交易的利润之和最大。那么就可以用第一题那种买卖一次股票的方式来求解。实际上就是求第 i 天以前的和第 i 天以后的最大利润之和,然后再娶个最大值。

但这里要注意,第一题里我们遍历到 i 的时候,实际上求得是第 i 天以前(包括第 i 天)交易一次所能获得的最大利润,而这道题我们还要求第 i 天以后(包括第 i 天)交易一次所能获得的最大利润,这里就不能正向遍历了,只能反向遍历。

如何反向遍历呢,就是定义一个 maxPrice,反向遍历来更新 maxPrice,(正向遍历是定义一个minPrice),这样只需要两次遍历就能求出结果,时间复杂度0(n)。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        if(n == 0) return 0;
        int minPrice = prices[0];
        int profitMax = 0;
        vector<int> leftProfit(prices.size(), 0);   // 存 i 天以前交易一次获得的最大利润
        for(int i = 1; i < prices.size(); i++) {
            profitMax = max(profitMax, prices[i] - minPrice);
            leftProfit[i] = profitMax;
            minPrice = min(prices[i], minPrice);
        }

        int maxPrice = prices[n-1];
        profitMax = 0;
        vector<int> rightProfit(prices.size(), 0);  // 存 i 天以后交易一次获得的最大利润
        for(int i = n-2; i >= 0; i--) {
            profitMax = max(profitMax, maxPrice - prices[i]);
            rightProfit[i] = profitMax;
            maxPrice = max(prices[i], maxPrice );
        }
        // 计算两者利润之和,再取最大值
        int maxPro = leftProfit[n-1];
        for(int i = 0; i < n-1; i++){
            maxPro = max(maxPro, leftProfit[i] + rightProfit[i+1]);
        }
        return maxPro;
    }
};

四、买卖股票的最佳时机 IV

leetcode 188题

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

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

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

1、三维数组DP

该问题是对问题三的推广,可用动态规划构建一个三维数组来解决
构建 dp[k][i][2]
其中k表示的是交易次数,i表示的是第几天,2表示的是有两种状态:手上没有股票/手上有股票
dp[k][i][0] 数组中的数字表示的是到了第i天,交易了k次,手上没有股票获得的最大利益
dp[k][i][1] 数组中的数字表示的是到了第i天,交易了k次,手上持有股票获得的最大利益

我们定义买入即视为一次操作,那么状态转移方程为:

i 天 t 次交易现在手上不持有 = max( i-1 天 t 次交易手上不持有,i-1 天 t 次交易手上持有 + i 天卖出价格prices)
dp[t][i][0] = max(dp[t][i - 1][0], dp[t][i - 1][1] + prices[i])

i 天 t 次交易现在手上持有 = max( i-1 天 t 次交易手上持有,i-1 天 t-1次 交易手上不持有 - i 天买入价格)
dp[t][i][1] = max(dp[t][i - 1][1], dp[t - 1][i - 1][0] - prices[i])

边界为:

dp[t][0][0] 0天t次交易,手上不持有:可能的 0
dp[t][0][1] 0天t次交易,手上持有:不可能(0天没有股票,所以无法买入持有;持有说明至少进行了一次买入,买入就交易,因此这里不可能【不可能意思就是不能从这里转移
dp[0][i][0] i天0次交易,手上不持有:0
dp[0][i][1] i天0次交易,手上持有:不可能(不交易手上不可能持有)

【注意】:当 k .>= prices.size() / 2 时,退化为无限次买卖,此时可用问题二的贪心来做,否则会造成数组太大,内存溢出

代码

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        if(!prices.size()) return 0;
        // 当 k >= prices.size()/2时,退化为无限次交易,此时可用问题二的贪心来做
        if(k >= prices.size()/2)
        {
            int sum = 0;
            for(int i = 1; i < prices.size(); i++)
            {
                if(prices[i] > prices[i - 1]) sum += prices[i] - prices[i - 1];
            }
            return sum;
        }

        vector<vector<vector<int>>> dp(k + 1, vector<vector<int>>(prices.size() + 1, vector<int>(2, 0)));
        for(int t = 0; t <= k; t++)
            dp[t][0][1] = INT_MIN;
        for(int i = 0; i <= prices.size(); i++)
            dp[0][i][1] = INT_MIN;

        for(int t = 1; t <= k; t++)
        {
            for(int i = 1; i <= prices.size(); i++)
            {
                //  注意这里的prices[i-1],是因为我们的dp是从1开始的,而prices是从0开始的
                dp[t][i][0] = max(dp[t][i - 1][0], dp[t][i - 1][1] + prices[i - 1]);
                dp[t][i][1] = max(dp[t][i - 1][1], dp[t - 1][i - 1][0] - prices[i - 1]);
            }
        }
        return dp[k][prices.size()][0];
    }
};

2、二维数组DP

二维数组DP是在三维数组DP的基础上合并买入卖出那两维构造的,将一次买和一次卖均视为一次交易,那么总的交易次数时k*2,注意,这里遍历k的时候,如果为奇数,代表当前执行买操作,持有股票,如果为偶数,代表当前执行卖操作,持有现金,即dp[t][i] 表示第 i 天 t 次交易的最大利润。

状态转移方程和边界条件也是依据三维来改的。

状态转移方程:

当 t 为奇数时,代表持有股票:
dp[t][i] = max(dp[t][i-1], dp[t-1][i-1] - prices[i])
当 t 为偶数时,代表没有股票:
dp[t][i] = max(dp[t][i-1], dp[t-1][i-1] + prices[i])

边界:

dp[0][0] = 0 0 天没有股票,利润为0
dp[t][0] = INT_MIN( t 为奇数) 0天t次交易,不可能

代码

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        if(!prices.size()) return 0;
        // 当 k >= prices.size()/2时,退化为无限次交易,此时可用问题二的贪心来做
        if(k >= prices.size()/2)
        {
            int sum = 0;
            for(int i = 1; i < prices.size(); i++)
            {
                if(prices[i] > prices[i - 1]) sum += prices[i] - prices[i - 1];
            }
            return sum;
        }

        vector<vector<int>> dp(2*k+1, vector<int>(prices.size()+1, 0));
        // 边界
        for(int t = 1; t <= 2*k; t += 2) dp[t][0] = INT_MIN;
        // 状态转移方程
        for(int t = 1; t <= 2*k; t++) {
            for(int i = 1; i <= prices.size(); i++) {
                if(t & 1 == 1) {  // 奇数
                    dp[t][i] = max(dp[t][i-1], dp[t-1][i-1] - prices[i-1]);
                } else {    // 偶数
                    dp[t][i] = max(dp[t][i-1], dp[t-1][i-1] + prices[i-1]);
                }
            }
        }

        return dp[2*k][prices.size()];
    }
};

五、买卖股票的最佳时机含冷冻期

leetcode 309题

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

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

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

这道题有三个交易状态,买入(持有股票)、卖出(没有股票)和冷冻期(持有股票或没有股票),那么就可以设置一个二维的dp数组 dp[n][3],其中
dp[i][0] 代表第 i 天状态为卖出所获得的最大利润,
dp[i][1] 代表第 i 天状态为买入所获得的最大利润,
dp[i][2] 代表第 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][2] - prices[i] }
dp[i][2] = { dp[i-1][0], dp[i-1][1], dp[i-1][2] }

边界:

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

代码

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if(prices.empty()) return 0;
        vector<vector<int>> dp(prices.size(), vector<int>(3, 0));
        // 边界
        dp[0][1] = -prices[0];
        // 状态转移方程
        for(int i = 1; i < prices.size(); 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][2] - prices[i]); // 持有股票
            dp[i][2] = max(dp[i-1][0], max(dp[i-1][1], dp[i-1][2])); // 冷冻期
        }
        return max(dp[prices.size()-1][0], dp[prices.size()-1][2]);
    }
};

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

leetcode 714题

这道题也是无限次买卖,但是因为加入了手续费,所以需要使用尽可能少的次数来达到最大利润,所以不能用贪心来做,贪心会增加交易次数,导致多扣除手续费。所以只能用dp来做,相比于题二,只需要让每次购买股票时的成本增加fee即可,也就是说每次买股票的时候就吧手续费扣掉,卖股票的时候不扣。

代码

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        vector<vector<int>> dp(prices.size(), vector<int>(2, 0));
        dp[0][0] = 0;
        dp[0][1] = -prices[0] - fee;
        for(int i = 1; i < prices.size(); 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[prices.size()-1][0];
    }
};

你可能感兴趣的:(leetcode,算法)