关于c++:Leetcode刷题股票问题系列总结

5次阅读

共计 6468 个字符,预计需要花费 17 分钟才能阅读完成。

1. 交易股票的最佳时机

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

int maxProfit(vector<int>& prices) {
    int min_price = INT_MAX, max_profit = 0;
    for(auto price : prices) {max_profit = max(max_profit, price - min_price);
        min_price = min(min_price, price);
    }
    return max_profit;
}

工夫复杂度:O(n),只须要遍历一次。
空间复杂度:O(1),只应用了常数个变量。

2. 交易股票的最佳时机 II

给定一个数组 prices,其中 prices[i] 示意股票第 i 天的价格。在每一天,你可能会决定购买和 / 或发售股票。你在任何时候 最多 只能持有 一股 股票。你也能够购买它,而后在同一天发售。返回你能取得的最大利润。

解法一:动静布局

思考到「不能同时参加多笔交易」,因而每天交易完结后只可能存在手里有一支股票或者没有股票的状态。

定义状态 dp[i][0] 示意第 i 天交易完后手里没有股票的最大利润,dp[i][1]示意第 i 天交易完后手里持有一支股票的最大利润(i 从 0 开始)。

思考 dp[i][0] 的转移方程,如果这一天交易完后手里没有股票,那么可能的转移状态为前一天曾经没有股票,即 dp[i-1][0],或者前一天完结的时候手里持有一支股票,即dp[i-1][1],这时候咱们要将其卖出,并取得price[i] 的收益。因而为了收益最大化,咱们列出如下的转移方程:

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

再来思考 dp[i][1],依照同样的形式思考转移状态,那么可能的转移状态为前一天曾经持有一支股票,即dp[i−1][1],或者前一天完结时还没有股票,即dp[i−1][0],这时候咱们要将其买入,并缩小prices[i] 的收益。能够列出如下的转移方程:

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

对于初始状态,依据状态定义咱们能够晓得第 00 天交易完结的时候 dp[0][0]=0dp[0][1]=−prices[0]

因而,咱们只有从前往后顺次计算状态即可。因为全副交易完结后,持有股票的收益肯定低于不持有股票的收益,因而这时候 dp[n−1][0] 的收益必然是大于 dp[n−1][1]的,最初的答案即为dp[n−1][0]

int maxProfit(vector<int>& prices) {int n = prices.size();
    int dp[n][2];
    dp[0][0] = 0, dp[0][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], dp[i - 1][0] - prices[i]);
    }
    return dp[n - 1][0];
}

工夫复杂度:O(n)
空间复杂度:O(n)。咱们须要开拓 O(n)空间存储动静布局中的所有状态。如果应用空间优化,空间复杂度能够优化至 O(1)。优化后如下:

int maxProfit(vector<int>& prices) {int n = prices.size();
    int dp0 = 0, dp1 = -prices[0];
    for (int i = 1; i < n; ++i) {int newDp0 = max(dp0, dp1 + prices[i]);
        int newDp1 = max(dp1, dp0 - prices[i]);
        dp0 = newDp0;
        dp1 = newDp1;
    }
    return dp0;
}

解法二:贪婪

因为股票的购买没有限度,贪婪的角度思考咱们每次抉择奉献大于 0 的区间即能使得答案最大化

int maxProfit(vector<int>& prices) {   
    int ans = 0;
    int n = prices.size();
    for (int i = 1; i < n; ++i) {ans += max(0, prices[i] - prices[i - 1]);
    }
    return ans;
}

工夫复杂度:O(n)
空间复杂度:O(1)

3. 交易股票的最佳时机 III

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多能够实现 两笔 交易。
留神:你不能同时参加多笔交易(你必须在再次购买前发售掉之前的股票)。

解法:动静布局

因为咱们最多能够实现两笔交易,因而在任意一天完结之后,咱们会处于以下五个状态中的一种:
1)未进行过任何操作;
2)只进行过一次买操作;
3)进行了一次买操作和一次卖操作,即实现了一笔交易;
4)在实现了一笔交易的前提下,进行了第二次买操作;
5)实现了全副两笔交易。

因为第一个状态的利润显然为 00,因而咱们能够不必将其记录,对于剩下的四个状态,咱们别离将它们的最大利润记为 buy1, sell1, buy2, sell2.

对于 buy1 而言,在第 i 天咱们能够不进行任何操作,放弃不变,也能够在未进行任何操作的前提下以 prices[i] 的价格买入股票,那么的状态转移方程即为:

        buy1 = max(buy1', -prices[i])

对于 sell1 而言,在第 i 天咱们能够不进行任何操作,放弃不变,也能够在只进行过一次买操作的前提下以 prices[i] 的价格卖出股票,那么 sell1 的状态转移方程即为:

        sell1 = max(sell1', buy1 + prices[i])

同理咱们能够失去 buy2, sell2 对应的状态转移方程:

        buy2 = max(buy2', sell1 - prices[i])
        sell2 = max(sell2', buy2 + prices[i])

那么对于边界条件,咱们思考第 i=0 地利的四个状态:buy1 即为以 prices[0] 的价格买入股票,因而 buy1=−prices[0]
sell1 即为在同一天买入并且卖出,因而 sell1=0buy2 即为在同一天买入并且卖出后再以 prices[0] 的价格买入股票,因而 buy2=−prices[0];同理可得 sell2=0。咱们将这四个状态作为边界条件,从 i=1 开始进行动静布局,即可失去答案。

在动静布局完结后,因为咱们能够进行不超过两笔交易,因而最终的答案在 0, sell1, sell2中,且为三者中的最大值。然而咱们能够发现,因为在边界条件中 sell1sell2的值曾经为 0,并且在状态转移的过程中咱们保护的是最大值,因而 sell1sell2最终肯定大于等于 0。同时,如果最优的状况对应的是恰好一笔交易,那么它也会因为咱们在转移时容许在同一天买入并且卖出这一宽松的条件,从 sell1转移至 sell2,因而最终的答案即为 sell2

int maxProfit(vector<int>& prices) {int n = prices.size();
    int buy1 = -prices[0], sell1 = 0;
    int buy2 = -prices[0], sell2 = 0;
    for (int i = 1; i < n; ++i) {buy1 = max(buy1, -prices[i]);
        sell1 = max(sell1, buy1 + prices[i]);
        buy2 = max(buy2, sell1 - prices[i]);
        sell2 = max(sell2, buy2 + prices[i]);
    }
    return sell2;
}

4. 交易股票的最佳时机 IV

给定一个整数数组 prices,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你最多能够实现 k 笔交易。留神:你不能同时参加多笔交易(你必须在再次购买前发售掉之前的股票)。

解法:动静布局(通用模版)

dp[j][k] 为 j 次、k 持股状态(0 不持股,1 持股)的金钱

则对于 dp[j][0],即 j 次不持股,显然起源有 j 次不持股 dp[j][0],和 j-1 次持股 dp[j-1][1],故咱们取起源的较大值即可。

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

对于 dp[j][1],即 j 次持股,起源有 j 次持股、j 次不持股,故

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

而每个状态都是一天,故咱们再引入一个维度 i 示意天,即 dp[i][j][k],而每天都是由前一天转移而来的。故显然

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

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

int maxProfit(int k, vector<int>& prices) {if (prices.empty()){return 0;}

    // i 天,j 次数,k 持股状态
    vector<vector<vector<int>>> dp(prices.size(),vector<vector<int>>(k+1,vector<int>(2)));

    for (int i=0;i<prices.size();i++){for (int j=1;j<k+1;j++){if (i==0){dp[i][j][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[prices.size()-1][k][0];
}

5. 最佳交易股票机会含冷冻期

给定一个整数数组 prices,其中第  prices[i] 示意第 i 天的股票价格。设计一个算法计算出最大利润。在满足以下约束条件下,你能够尽可能地实现更多的交易(屡次交易一支股票):
卖出股票后,你无奈在第二天买入股票 (即冷冻期为 1 天)。
留神:你不能同时参加多笔交易(你必须在再次购买前发售掉之前的股票)。

解法:动静布局

咱们用 f[i] 示意第 i 天完结之后的「累计最大收益」。依据题目形容,因为咱们最多只能同时买入(持有)一支股票,并且卖出股票后有冷冻期的限度,因而咱们会有三种不同的状态:

1)目前持有一支股票,对应的「累计最大收益」记为 f[i][0]
2)目前不持有任何股票,并且处于冷冻期中,对应的「累计最大收益」记为 f[i][1]
3)目前不持有任何股票,并且不处于冷冻期中,对应的「累计最大收益」记为 f[i][2]

如何进行状态转移呢?在第 i 地利,咱们能够在不违反规定的前提下进行「买入」或者「卖出」操作,此时第 ii 天的状态会从第 i-1 天的状态转移而来;咱们也能够不进行任何操作,此时第 i 天的状态就等同于第 i-1 天的状态。那么咱们别离对这三种状态进行剖析:

对于 f[i][0],咱们目前持有的这一支股票能够是在第 i−1 天就曾经持有的,对应的状态为 f[i-1][0];或者是第 i 天买入的,那么第 i−1 天就不能持有股票并且不处于冷冻期中,对应的状态为 f[i−1][2] 加上买入股票的负收益 prices[i]。因而状态转移方程为:

    f[i][0] = max(f[i - 1][0], f[i - 1][2] - prices[i])

对于 f[i][1],咱们在第 i 天完结之后处于冷冻期的起因是在当天卖出了股票,那么阐明在第 i−1 地利咱们必须持有一支股票,对应的状态为 f[i-1][0] 加上卖出股票的正收益 prices[i]。因而状态转移方程为:

    f[i][1] = f[i - 1][0] + prices[i]

对于 f[i][2],咱们在第 i 天完结之后不持有任何股票并且不处于冷冻期,阐明当天没有进行任何操作,即第 i−1 地利不持有任何股票:如果处于冷冻期,对应的状态为 f[i−1][1];如果不处于冷冻期,对应的状态为 f[i−1][2]。因而状态转移方程为:

    f[i][2] = max(f[i - 1][1], f[i - 1][2])

留神到如果在最初一天(第 n−1 天)完结之后,手上依然持有股票,那么显然是没有任何意义的。因而更加准确地,最终的答案实际上是 f[n-1][1]f[n-1][2] 中的较大值

    max(f[n - 1][1], f[n - 1][2])

边界条件:在第 0 地利,如果持有股票,那么只能是在第 0 天买入的,对应负收益 −prices[0];如果不持有股票,那么收益为零。

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

这样咱们就能够从第 1 天开始,依据下面的状态转移方程进行进行动静布局,直到计算出第 n−1 天的后果。

int maxProfit(vector<int>& prices) {if (prices.empty()) {return 0;}

    int n = prices.size();
    // f[i][0]: 手上持有股票的最大收益
    // f[i][1]: 手上不持有股票,并且处于冷冻期中的累计最大收益
    // f[i][2]: 手上不持有股票,并且不在冷冻期中的累计最大收益
    vector<vector<int>> f(n, vector<int>(3));
    f[0][0] = -prices[0];
    for (int i = 1; i < n; ++i) {f[i][0] = max(f[i - 1][0], f[i - 1][2] - prices[i]);
        f[i][1] = f[i - 1][0] + prices[i];
        f[i][2] = max(f[i - 1][1], f[i - 1][2]);
    }
    return max(f[n - 1][1], f[n - 1][2]);
}

6. 交易股票的最佳时机含手续费

给定一个整数数组 prices,其中 prices[i] 示意第 i 天的股票价格;整数 fee 代表了交易股票的手续费用。你能够有限次地实现交易,然而你每笔交易都须要付手续费。如果你曾经购买了一个股票,在卖出它之前你就不能再持续购买股票了。返回取得利润的最大值。
留神:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只须要为领取一次手续费。

解法:动静布局

本题和 交易股票的最佳时机 II 是十分相似的题,惟一的区别就在于本题有「手续费」。在 题目 II 的根底之上,dp[i][0]的状态转移方程中减去手续费即可。思考到「不能同时参加多笔交易」,因而每天交易完结后只可能存在手里有一支股票或者没有股票的状态。

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

参考资料:

链接:https://leetcode-cn.com/probl…

链接:https://leetcode-cn.com/probl…

链接:https://leetcode-cn.com/probl…

链接:https://leetcode-cn.com/probl…

链接:https://leetcode-cn.com/probl…

链接:https://leetcode-cn.com/probl…

正文完
 0