代码随想录 | Day 49 - LeetCode 121. 买卖股票的最佳时机、LeetCode 122.买卖股票的最佳时机II

        今天的2道题是一个套路,两者唯一的区别就是“是否可以多次买卖”。这一区别最终体现在了状态转移方程上买入的情况,是从0开始买入还是继承之前的利润再买入。所以在做题的时候要仔细看下题目要求,是只能一次,还是可以多次买卖。


        第1题(LeetCode 121. 买卖股票的最佳时机)看似是很简单的一道题,但自己除了双重循环的暴力解法外,没有想到其他可行的方法。题解中除了暴力解法外,还有两种方法。第1种是贪心方法,需要在遍历prices时不断更新最小价格,并根据“当前价格”与“最小价格”的差来更新答案。因为最小价格在更新时只会变得更小,所以如果“当前价格”等于或大于之前结果对应的卖出价格时,“当前价格”与“最小价格”的差只会更大。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int priceMin = prices[0], res = 0;
        for (int price : prices) {
            priceMin = min(priceMin, price);
            res = max(res, price - priceMin);
        }
        return res;
    }
};

        第2种是DP解法。dp数组的第i天有两个数值,dp[i][0]表示第i天“不持有”股票对应的最大所得现金,dp[i][1]表示第i天“持有”股票对应的最大所得现金。

  • 这里的持有就是字面意思,第i天持有分2种情况。有可能之前就买入,即第(i - 1)天就持有了(dp[i - 1][1]);也有可能是之前未买入,第i天买入(-prices[i])。
  • 不持有也分两种情况。有可能前一天就不持有(dp[i - 1][0]);也有可能前一天持有,但第i天卖出(dp[i - 1][1] + prices[i])。

        注意“第i天持有”的第2种情况,因为这一题中只能买卖一次,所以“之前未买入”就代表“之前没有买也没有卖”,相当于之前的存款数额为0,所以应该对应-prices[i],而不是dp[i - 1][0] - prices[i]。所以状态转移方程就可以总结为dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i])和dp[i][1] = max(dp[i - 1][1], -prices[i])。

        初始化方面,第0天“不持有”对应的现金数额为0,“持有”则对应买入第1天的股票,现金数额为-prices[0]。由于每个dp[i]的取值都依赖于其前一个,所以遍历方向为正向遍历。最终是要卖出的,对应“不持有”,所以最后返回dp的最后一行第0列数值作为结果。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        vector<vector<int>> dp(prices.size(), vector<int>(2, 0));
        dp[0][0] = 0; // [i][0]表示第i天“不持有”股票对应的最大所得现金
        dp[0][1] = -prices[0]; // [i][1]表示第i天“持有”股票对应的最大所得现金
        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], -prices[i]);
        }
        return dp[prices.size() - 1][0];
    }
};

         也正是由于每个dp[i]的取值都依赖于其前一个,所以dp矩阵没必要设置行数与prices保持一致,只需要2行即可。在实现上,可以用“下标对2取余”的“滚动数组”方式。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        vector<vector<int>> dp(2, vector<int>(2, 0));
        dp[0][0] = 0; // [i][0]表示第i天“不持有”股票对应的最大所得现金
        dp[0][1] = -prices[0]; // [i][1]表示第i天“持有”股票对应的最大所得现金
        for (int i = 1; i < prices.size(); ++i) {
            dp[i % 2][0] = max(dp[(i - 1) % 2][0], dp[(i - 1) % 2][1] + prices[i]); // prices下标不对2取余
            dp[i % 2][1] = max(dp[(i - 1) % 2][1], -prices[i]); // prices下标不对2取余
        }
        return dp[(prices.size() - 1) % 2][0];
    }
};

        第2题(LeetCode 122.买卖股票的最佳时机II)在day 37中用贪心方法解决过,这里再来尝试用DP方法。只有一处要求与上一题不同,就是股票可以买卖多次(但任意时刻还是最多只能持有一个股票)。所以“第i天持有”的第2种情况,也就是“之前未买入,第i天买入”需要改为“第(i - 1)天未持有,第i天买入”。也就是说,第i天就不再是在“从未买卖过”的基础上进行了,而是可以在“获得之前买卖利润”的基础上来进行“第i天的买入”。所以这一部分的取值对应dp[i - 1][0] - prices[i]。

        下面直接写出“滚动数组”方式实现的代码。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        vector<vector<int>> dp(2, vector<int>(2, 0));
        dp[0][0] = 0; // [i][0]表示第i天“不持有”股票对应的最大所得现金
        dp[0][1] = -prices[0]; // [i][1]表示第i天“持有”股票对应的最大所得现金
        for (int i = 1; i < prices.size(); ++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) % 2][0] - prices[i]);
        }
        return dp[(prices.size() - 1) % 2][0];
    }
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值