跳转至

LeetCode 股票买卖问题*

题目描述*

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

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

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

以上是几个题目共同的条件,区别就是添加了限制,第一题是 k = 1,第二题是 k = +infinity,第三题是 k = 2,其余两道加入了交易冷冻期和手续费的条件。

我们准备用一个思路解决这些问题,下面开始分析。

状态表示*

利用状态进行穷举,分析每一天有几种状态,每种状态对应的选择,根据选择更新状态。

此问题中,每天有三种选择:买入、卖出、不动。同时还有一些限定,先买入再卖出再买入,不动还分持有和不持有,还有交易次数 k 的限制。

dp[i][k][1/0] 表示第 i 天,交易次数限制为 k,是/否持有股票的状态下的利益。一共就 n _ k _ 2 种状态。

我们要求的是 dp[n -1][k][0],即最后一天,最多允许 k 次交易,最多获得的利润。

状态转移方程*

dp[i][k][0] = max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i])

第 i 天不持有股票,可以是前一天就没有股票,今天不动,还是没有;或者是前一天有股票,今天卖出。

dp[i][k][1] = max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i])

第 i 天持有股票,前一天有,今天不动;或者前一天没有,今天买入。

今天的选择就是二者较大值。

初始状态*

dp[-1][k][0] = 0,未开始的利润为 0;dp[-1][k][1] = INT_MIN,未开始不可能有股票;dp[i][0][0] = 0,不允许交易利润为 0;dp[i][0][1],不允许交易不可能持有股票。

因此总结状态转移方程:

dp[-1][k][0] = dp[i][0][0] = 0;
dp[-1][k][1] = dp[i][0][1] = INT_MIN;

dp[i][k][0] = max(dp[i - 1][k][0], dp[i -1][k][1] + prices[i]);
dp[i][k][1] = max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i]);

基本的框架形成,下面开始完善细节问题。

k = 1*

对状态转移方程化简:

dp[i][1][0] = max(dp[i - 1][1][0], dp[i - 1][1][1] + prices[i]);
dp[i][1][1] = max(dp[i - 1][1][1], dp[i - 1][0][0] - prices[i]);
            = max(dp[i - 1][1][1], -prices[i]);

// 去掉 k:
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
dp[i][1] = max(dp[i - 1][1], -prices[i]);

写出对应的代码:

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

k = +infinity*

k 为正无穷,那我们就不需要记录这个状态,状态转移方程变为:

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]);

直接写空间复杂度 O(1) 的代码:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        if(n <= 1) {
            return 0;
        }
        int dp_i_0 = 0, dp_i_1 = INT_MIN;
        for(int i = 0; i < n; i++) {
            int tmp = dp_i_0;
            dp_i_0 = max(dp_i_0, dp_i_1 + prices[i]);
            dp_i_1 = max(dp_i_1, tmp - prices[i]);
        }
        return dp_i_0;
    }
};

k = 2*

上两个都和 k 关系不大,这个就需要处理 k 。状态转移方程无法化简。

dp[i][k][0] = max(dp[i - 1][k][0], dp[i -1][k][1] + prices[i]);
dp[i][k][1] = max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i]);

dp[0][k][0] = max(dp[-1][k][0], dp[-1][k][1] + prices[i]) = 0;
dp[0][k][1] = max(dp[-1][k][1], dp[-1][k - 1][0] - prices[i]) = - prices[i];

dp[i][2][0] = max(dp[i - 1][2][0], dp[i - 1][2][1] + prices[i]);
dp[i][2][1] = max(dp[i - 1][2][1], dp[i - 1][1][0] - prices[i]);
dp[i][1][0] = max(dp[i - 1][1][0], dp[i - 1][1][1] + prices[i]);
dp[i][1][1] = max(dp[i - 1][1][1], - prices[i]);

我们不仅需要对 i 遍历,还需要遍历 k,要注意初始状态。当然 k 比较小,我们还可以直接列出来。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        if(n <= 1) {
            return 0;
        }
        int dp[n][3][2] = { 0 };
        for(int i = 0; i < n; i++) {
            for(int k = 2; k >= 1; k--) {
                if(i == 0) {
                    dp[i][k][0] = 0;
                    dp[i][k][1] = -1 * prices[i];
                    continue;
                }
                dp[i][k][0] = max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i]);
                dp[i][k][1] = max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i]);
            }
        }
        return dp[n - 1][2][0];
    }
};
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int dp_i10 = 0, dp_i11 = INT_MIN;
        int dp_i20 = 0, dp_i21 = INT_MIN;
        for(auto price : prices) {
            dp_i20 = max(dp_i20, dp_i21 + price);
            dp_i21 = max(dp_i21, dp_i10 - price);
            dp_i10 = max(dp_i10, dp_i11 + price);
            dp_i11 = max(dp_i11, -1 * price);
        }
        return dp_i20;
    }
};

任意给定的 k*

一次交易买入卖出至少需要两天,所以当 k > n / 2 时,就等价于不限制次数了。对于有效限制的部分就按照上面 k = 2 时穷举即可。

class Solution {
public:
    int maxProfitWithoutK(vector<int>& prices) {
        int dp_i_0 = 0, dp_i_1 = INT_MIN;
        for(int i = 0; i < prices.size(); i++) {
            dp_i_0 = max(dp_i_0, dp_i_1 + prices[i]);
            dp_i_1 = max(dp_i_1, dp_i_0 - prices[i]);
        }
        return dp_i_0;
    }
    int maxProfit(int k, vector<int>& prices) {
        int n = prices.size();
        if(n <= 1) {
            return 0;
        }
        if(k > n / 2) {
            return maxProfitWithoutK(prices);
        }
        int dp[n][k + 1][2] = { 0 };
        for(int i = 0; i < n; i++) {
            for(int j = k; j >= 1; j--) {
                if(i == 0) {
                    dp[i][j][0] = 0;
                    dp[i][j][1] = -1 * prices[i];
                    continue;
                }
                dp[i][j][0] = max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i]);
                dp[i][j][1] = max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i]);
            }
        }
        return dp[n - 1][k][0];
    }
};

k = +infinity 含冷冻期*

卖出股票后,第二天不能买入股票。稍微修改一下前面 k = +infinity 的状态方程。

```c++ hl_line="2" 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]);

添加一个变量记录前天的即可。

```c++
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        if(n <= 1) {
            return 0;
        }
        int dp_i_0 = 0, dp_i_1 = INT_MIN;
        int dp_i2_0 = 0; // 记录前天的
        for(int i = 0; i < n; i++) {
            int tmp = dp_i_0;
            dp_i_0 = max(dp_i_0, dp_i_1 + prices[i]);
            dp_i_1 = max(dp_i_1, dp_i2_0 - prices[i]);
            dp_i2_0 = tmp;
        }
        return dp_i_0;
    }
};

k = +infinity 含手续费*

每次交易要付手续费,也比较简单,只要再买入的时候减去手续费即可。

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);
class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        int n = prices.size();
        if(n <= 1) {
            return 0;
        }
        int dp_i_0 = 0, dp_i_1 = INT_MIN;
        for(int i = 0; i < n; i++) {
            int tmp = dp_i_0;
            dp_i_0 = max(dp_i_0, dp_i_1 + prices[i]);
            dp_i_1 = max(dp_i_1, tmp - prices[i] - fee);
        }
        return dp_i_0;
    }
};

最后更新: July 23, 2022